MatroskaKeyframeExtractor.cs 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions;
  5. using Jellyfin.MediaEncoding.Keyframes.Matroska.Models;
  6. using NEbml.Core;
  7. namespace Jellyfin.MediaEncoding.Keyframes.Matroska;
  8. /// <summary>
  9. /// The keyframe extractor for the matroska container.
  10. /// </summary>
  11. public static class MatroskaKeyframeExtractor
  12. {
  13. /// <summary>
  14. /// Extracts the keyframes in ticks (scaled using the container timestamp scale) from the matroska container.
  15. /// </summary>
  16. /// <param name="filePath">The file path.</param>
  17. /// <returns>An instance of <see cref="KeyframeData"/>.</returns>
  18. public static KeyframeData GetKeyframeData(string filePath)
  19. {
  20. using var stream = File.OpenRead(filePath);
  21. using var reader = new EbmlReader(stream);
  22. var seekHead = reader.ReadSeekHead();
  23. // External lib does not support seeking backwards (yet)
  24. Info info;
  25. ulong videoTrackNumber;
  26. if (seekHead.InfoPosition < seekHead.TracksPosition)
  27. {
  28. info = reader.ReadInfo(seekHead.InfoPosition);
  29. videoTrackNumber = reader.FindFirstTrackNumberByType(seekHead.TracksPosition, MatroskaConstants.TrackTypeVideo);
  30. }
  31. else
  32. {
  33. videoTrackNumber = reader.FindFirstTrackNumberByType(seekHead.TracksPosition, MatroskaConstants.TrackTypeVideo);
  34. info = reader.ReadInfo(seekHead.InfoPosition);
  35. }
  36. var keyframes = new List<long>();
  37. reader.ReadAt(seekHead.CuesPosition);
  38. reader.EnterContainer();
  39. while (reader.FindElement(MatroskaConstants.CuePoint))
  40. {
  41. reader.EnterContainer();
  42. ulong? trackNumber = null;
  43. // Mandatory element
  44. reader.FindElement(MatroskaConstants.CueTime);
  45. var cueTime = reader.ReadUInt();
  46. // Mandatory element
  47. reader.FindElement(MatroskaConstants.CueTrackPositions);
  48. reader.EnterContainer();
  49. if (reader.FindElement(MatroskaConstants.CuePointTrackNumber))
  50. {
  51. trackNumber = reader.ReadUInt();
  52. }
  53. reader.LeaveContainer();
  54. if (trackNumber == videoTrackNumber)
  55. {
  56. keyframes.Add(ScaleToTicks(cueTime, info.TimestampScale));
  57. }
  58. reader.LeaveContainer();
  59. }
  60. reader.LeaveContainer();
  61. var result = new KeyframeData(ScaleToTicks(info.Duration ?? 0, info.TimestampScale), keyframes);
  62. return result;
  63. }
  64. private static long ScaleToTicks(ulong unscaledValue, long timestampScale)
  65. {
  66. // TimestampScale is in nanoseconds, scale it to get the value in ticks, 1 tick == 100 ns
  67. return (long)unscaledValue * timestampScale / 100;
  68. }
  69. private static long ScaleToTicks(double unscaledValue, long timestampScale)
  70. {
  71. // TimestampScale is in nanoseconds, scale it to get the value in ticks, 1 tick == 100 ns
  72. return Convert.ToInt64(unscaledValue * timestampScale / 100);
  73. }
  74. }