private string GetInputPathArgument(EncodingJob state) { var protocol = state.InputProtocol; var mediaPath = state.MediaPath ?? string.Empty; var inputPath = new[] { mediaPath }; if (state.IsInputVideo) { if (!(state.VideoType == VideoType.Iso && state.IsoMount == null)) { inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames); } } return(MediaEncoder.GetInputArgument(inputPath, protocol)); }
/// <summary> /// Gets the input argument. /// </summary> /// <param name="item">The item.</param> /// <param name="isoMount">The iso mount.</param> /// <returns>System.String.</returns> protected string GetInputArgument(BaseItem item, IIsoMount isoMount) { var type = InputType.AudioFile; var inputPath = new[] { item.Path }; var video = item as Video; if (video != null) { if (!(video.VideoType == VideoType.Iso && isoMount == null)) { inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type); } } return(MediaEncoder.GetInputArgument(inputPath, type)); }
/// <summary> /// Gets the probe size argument. /// </summary> /// <param name="item">The item.</param> /// <returns>System.String.</returns> protected string GetProbeSizeArgument(BaseItem item) { var type = InputType.AudioFile; if (item is Audio) { type = MediaEncoderHelpers.GetInputType(item.Path, null, null); } else { var video = item as Video; if (video != null) { type = MediaEncoderHelpers.GetInputType(item.Path, video.VideoType, video.IsoType); } } return(MediaEncoder.GetProbeSizeArgument(type)); }
private async Task <InternalMediaInfoResult> GetMediaInfo(BaseItem item, IIsoMount isoMount, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var idString = item.Id.ToString("N"); var cachePath = Path.Combine(_appPaths.CachePath, "ffprobe-video", idString.Substring(0, 2), idString, "v" + SchemaVersion + _mediaEncoder.Version + item.DateModified.Ticks.ToString(_usCulture) + ".json"); try { return(_json.DeserializeFromFile <InternalMediaInfoResult>(cachePath)); } catch (FileNotFoundException) { } catch (DirectoryNotFoundException) { } var type = InputType.File; var inputPath = isoMount == null ? new[] { item.Path } : new[] { isoMount.MountedPath }; var video = item as Video; if (video != null) { inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type); } var result = await _mediaEncoder.GetMediaInfo(inputPath, type, false, cancellationToken).ConfigureAwait(false); Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); _json.SerializeToFile(result, cachePath); return(result); }
/// <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> /// <returns>Task.</returns> protected Task Fetch(Audio audio, CancellationToken cancellationToken, InternalMediaInfoResult data) { var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams; audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.Video); if (data.streams != null) { // Get the first audio stream var stream = data.streams.FirstOrDefault(s => string.Equals(s.codec_type, "audio", StringComparison.OrdinalIgnoreCase)); if (stream != null) { // 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, _usCulture)).Ticks; } } } if (data.format.tags != null) { FetchDataFromTags(audio, data.format.tags); } return(_itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken)); }
public async Task <DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken) { var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false); try { // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in. // Always use 10 seconds for dvd because our duration could be out of whack var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue && item.RunTimeTicks.Value > 0 ? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1)) : TimeSpan.FromSeconds(10); var protocol = item.LocationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File; var inputPath = MediaEncoderHelpers.GetInputArgument(item.Path, protocol, isoMount, item.PlayableStreamFileNames); var stream = await _mediaEncoder.ExtractVideoImage(inputPath, protocol, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); return(new DynamicImageResponse { Format = ImageFormat.Jpg, HasImage = true, Stream = stream }); } finally { if (isoMount != null) { isoMount.Dispose(); } } }
public async Task<DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken) { var protocol = item.PathProtocol ?? MediaProtocol.File; var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, item.Path, null, item.GetPlayableStreamFileNames(_mediaEncoder)); var mediaStreams = item.GetMediaStreams(); var imageStreams = mediaStreams .Where(i => i.Type == MediaStreamType.EmbeddedImage) .ToList(); var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("front", StringComparison.OrdinalIgnoreCase) != -1) ?? imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1) ?? imageStreams.FirstOrDefault(); string extractedImagePath; if (imageStream != null) { // Instead of using the raw stream index, we need to use nth video/embedded image stream var videoIndex = -1; foreach (var mediaStream in mediaStreams) { if (mediaStream.Type == MediaStreamType.Video || mediaStream.Type == MediaStreamType.EmbeddedImage) { videoIndex++; } if (mediaStream == imageStream) { break; } } extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, protocol, imageStream, videoIndex, cancellationToken).ConfigureAwait(false); } else { // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in. // Always use 10 seconds for dvd because our duration could be out of whack var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue && item.RunTimeTicks.Value > 0 ? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1)) : TimeSpan.FromSeconds(10); var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, protocol, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); } return new DynamicImageResponse { Format = ImageFormat.Jpg, HasImage = true, Path = extractedImagePath, Protocol = MediaProtocol.File }; }
public async Task <bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List <ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken) { if (!IsEligibleForChapterImageExtraction(video)) { extractImages = false; } var success = true; var changesMade = false; var runtimeTicks = video.RunTimeTicks ?? 0; var currentImages = GetSavedChapterImages(video, directoryService); foreach (var chapter in chapters) { if (chapter.StartPositionTicks >= runtimeTicks) { _logger.LogInformation("Stopping chapter extraction for {0} because a chapter was found with a position greater than the runtime.", video.Name); break; } var path = GetChapterImagePath(video, chapter.StartPositionTicks); if (!currentImages.Contains(path, StringComparer.OrdinalIgnoreCase)) { if (extractImages) { cancellationToken.ThrowIfCancellationRequested(); try { // Add some time for the first chapter to make sure we don't end up with a black image var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks); var protocol = MediaProtocol.File; var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, Array.Empty <string>()); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); var container = video.Container; var tempFile = await _encoder.ExtractVideoImage(inputPath, container, protocol, video.GetDefaultVideoStream(), video.Video3DFormat, time, cancellationToken).ConfigureAwait(false); _fileSystem.CopyFile(tempFile, path, true); try { _fileSystem.DeleteFile(tempFile); } catch { } chapter.ImagePath = path; chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path); changesMade = true; } catch (Exception ex) { _logger.LogError(ex, "Error extracting chapter images for {0}", string.Join(",", video.Path)); success = false; break; } } else if (!string.IsNullOrEmpty(chapter.ImagePath)) { chapter.ImagePath = null; changesMade = true; } } else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase)) { chapter.ImagePath = path; chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path); changesMade = true; } } if (saveChapters && changesMade) { _chapterManager.SaveChapters(video.Id.ToString(), chapters); } DeleteDeadImages(currentImages, chapters); return(success); }
protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount, BlurayDiscInfo blurayInfo, MetadataRefreshOptions options) { var mediaInfo = MediaEncoderHelpers.GetMediaInfo(data); var mediaStreams = mediaInfo.MediaStreams; video.TotalBitrate = mediaInfo.TotalBitrate; video.FormatName = (mediaInfo.Format ?? string.Empty) .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase); 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, _usCulture)).Ticks; } if (video.VideoType == VideoType.VideoFile) { var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.'); video.Container = extension; } else { video.Container = null; } if (!string.IsNullOrEmpty(data.format.size)) { video.Size = long.Parse(data.format.size, _usCulture); } else { video.Size = null; } } var mediaChapters = (data.Chapters ?? new MediaChapter[] { }).ToList(); var chapters = mediaChapters.Select(GetChapterInfo).ToList(); if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay)) { FetchBdInfo(video, chapters, mediaStreams, blurayInfo); } await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); FetchWtvInfo(video, data); video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270); var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); video.VideoBitRate = videoStream == null ? null : videoStream.BitRate; video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index; video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle); ExtractTimestamp(video); UpdateFromMediaInfo(video, videoStream); await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false); if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || options.MetadataRefreshMode == MetadataRefreshMode.Default) { var chapterOptions = _chapterManager.GetConfiguration(); try { var remoteChapters = await DownloadChapters(video, chapters, chapterOptions, cancellationToken).ConfigureAwait(false); if (remoteChapters.Count > 0) { chapters = remoteChapters; } } catch (Exception ex) { _logger.ErrorException("Error downloading chapters", ex); } if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video)) { AddDummyChapters(video, chapters); } NormalizeChapterNames(chapters); await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions { Chapters = chapters, Video = video, ExtractImages = chapterOptions.ExtractDuringLibraryScan, SaveChapters = false }, cancellationToken).ConfigureAwait(false); await _chapterManager.SaveChapters(video.Id.ToString(), chapters, cancellationToken).ConfigureAwait(false); } }
/// <summary> /// Gets the probe size argument. /// </summary> /// <param name="item">The item.</param> /// <returns>System.String.</returns> protected string GetProbeSizeArgument(BaseItem item) { return(MediaEncoder.GetProbeSizeArgument(MediaEncoderHelpers.GetInputType(item))); }
public async Task <bool> RefreshChapterImages(ChapterImageRefreshOptions options, CancellationToken cancellationToken) { var extractImages = options.ExtractImages; var video = options.Video; var chapters = options.Chapters; var saveChapters = options.SaveChapters; if (!IsEligibleForChapterImageExtraction(video)) { extractImages = false; } var success = true; var changesMade = false; var runtimeTicks = video.RunTimeTicks ?? 0; var currentImages = GetSavedChapterImages(video); foreach (var chapter in chapters) { if (chapter.StartPositionTicks >= runtimeTicks) { _logger.Info("Stopping chapter extraction for {0} because a chapter was found with a position greater than the runtime.", video.Name); break; } var path = GetChapterImagePath(video, chapter.StartPositionTicks); if (!currentImages.Contains(path, StringComparer.OrdinalIgnoreCase)) { if (extractImages) { if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso || video.VideoType == VideoType.BluRay) { continue; } // Add some time for the first chapter to make sure we don't end up with a black image var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks); var protocol = MediaProtocol.File; var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, video.PlayableStreamFileNames); try { _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); using (var stream = await _encoder.ExtractVideoImage(inputPath, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false)) { using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { await stream.CopyToAsync(fileStream).ConfigureAwait(false); } } chapter.ImagePath = path; changesMade = true; } catch (Exception ex) { _logger.ErrorException("Error extracting chapter images for {0}", ex, string.Join(",", inputPath)); success = false; break; } } else if (!string.IsNullOrEmpty(chapter.ImagePath)) { chapter.ImagePath = null; changesMade = true; } } else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase)) { chapter.ImagePath = path; changesMade = true; } } if (saveChapters && changesMade) { await _chapterManager.SaveChapters(video.Id.ToString(), chapters, cancellationToken).ConfigureAwait(false); } DeleteDeadImages(currentImages, chapters); return(success); }
private async Task <string> GetBifFile(GetBifFile request) { var widthVal = request.MaxWidth.HasValue ? request.MaxWidth.Value.ToString(CultureInfo.InvariantCulture) : string.Empty; var item = _libraryManager.GetItemById(request.Id); var mediaSources = ((IHasMediaSources)item).GetMediaSources(false).ToList(); var mediaSource = mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId)) ?? mediaSources.First(); var path = Path.Combine(_appPaths.ImageCachePath, "bif", request.Id, request.MediaSourceId, widthVal, "index.bif"); if (File.Exists(path)) { return(path); } var protocol = mediaSource.Protocol; var inputPath = MediaEncoderHelpers.GetInputArgument(mediaSource.Path, protocol, null, mediaSource.PlayableStreamFileNames); var semaphore = GetLock(path); await semaphore.WaitAsync().ConfigureAwait(false); try { if (File.Exists(path)) { return(path); } await _mediaEncoder.ExtractVideoImagesOnInterval(inputPath, protocol, mediaSource.Video3DFormat, TimeSpan.FromSeconds(10), Path.GetDirectoryName(path), "img_", request.MaxWidth, CancellationToken.None) .ConfigureAwait(false); var images = new DirectoryInfo(Path.GetDirectoryName(path)) .EnumerateFiles() .Where(img => string.Equals(img.Extension, ".jpg", StringComparison.Ordinal)) .OrderBy(i => i.FullName) .ToList(); using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { var magicNumber = new byte[] { 0x89, 0x42, 0x49, 0x46, 0x0d, 0x0a, 0x1a, 0x0a }; await fs.WriteAsync(magicNumber, 0, magicNumber.Length); // version var bytes = GetBytes(0); await fs.WriteAsync(bytes, 0, bytes.Length); // image count bytes = GetBytes(images.Count); await fs.WriteAsync(bytes, 0, bytes.Length); // interval in ms bytes = GetBytes(10000); await fs.WriteAsync(bytes, 0, bytes.Length); // reserved for (var i = 20; i <= 63; i++) { bytes = new byte[] { 0x00 }; await fs.WriteAsync(bytes, 0, bytes.Length); } // write the bif index var index = 0; long imageOffset = 64 + (8 * images.Count) + 8; foreach (var img in images) { bytes = GetBytes(index); await fs.WriteAsync(bytes, 0, bytes.Length); bytes = GetBytes(imageOffset); await fs.WriteAsync(bytes, 0, bytes.Length); imageOffset += img.Length; index++; } bytes = new byte[] { 0xff, 0xff, 0xff, 0xff }; await fs.WriteAsync(bytes, 0, bytes.Length); bytes = GetBytes(imageOffset); await fs.WriteAsync(bytes, 0, bytes.Length); // write the images foreach (var img in images) { using (var imgStream = _fileSystem.GetFileStream(img.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) { await imgStream.CopyToAsync(fs).ConfigureAwait(false); } } } return(path); } finally { semaphore.Release(); } }