From 124bc001aa18f3caaca65226bd001fd808de717a Mon Sep 17 00:00:00 2001 From: feyris-tan <4116042+feyris-tan@users.noreply.github.com> Date: Sun, 28 Jun 2026 12:37:47 +0200 Subject: [PATCH] Began parsing Threemedia OTT fragments. --- .../ThreemediaOtt/ThreemediaBaseStream.cs | 90 +++++++++++++++++++ .../ThreemediaContentFragment.cs | 37 ++++++++ .../ThreemediaContentFragmentStream.cs | 58 ++++++++++++ .../ThreemediaOtt/ThreemediaOttHandler.cs | 7 +- .../ThreemediaOtt/ThreemediaSession.cs | 7 ++ .../ThreemediaOtt/ThreemediaSessionStream.cs | 26 ++++++ 6 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 skyscraper8/ThreemediaOtt/ThreemediaBaseStream.cs create mode 100644 skyscraper8/ThreemediaOtt/ThreemediaContentFragmentStream.cs create mode 100644 skyscraper8/ThreemediaOtt/ThreemediaSessionStream.cs diff --git a/skyscraper8/ThreemediaOtt/ThreemediaBaseStream.cs b/skyscraper8/ThreemediaOtt/ThreemediaBaseStream.cs new file mode 100644 index 0000000..c42acf5 --- /dev/null +++ b/skyscraper8/ThreemediaOtt/ThreemediaBaseStream.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.ThreemediaOtt +{ + internal abstract class ThreemediaBaseStream : Stream + { + public override void Flush() + { + throw new NotSupportedException(); + } + + public override long Position + { + get + { + return _internalPosition; + } + set + { + if (value > calculatedLength) + { + throw new ArgumentOutOfRangeException(nameof(value), value, String.Format("This stream is capped at {0}", calculatedLength)); + } + + Seek(value, SeekOrigin.Begin); + } + } + + public override long Seek(long offset, SeekOrigin origin) + { + long newPosition; + switch (origin) + { + case SeekOrigin.Begin: + newPosition = offset; + break; + case SeekOrigin.Current: + newPosition = Position + offset; + break; + case SeekOrigin.End: + newPosition = Length + offset; + break; + default: + throw new NotImplementedException(String.Format("Unknown seek operation: {0}", origin)); + } + + if (newPosition < 0 || newPosition > calculatedLength) + { + throw new IOException("An attempt was made to move the file pointer before the beginning or past the end of the stream."); + } + + SeekHook(); + + return newPosition; + } + + protected abstract void SeekHook(); + + private bool lengthSet; + private long calculatedLength; + public override void SetLength(long value) + { + if (lengthSet) + { + throw new NotSupportedException(); + } + else + { + calculatedLength = value; + lengthSet = true; + } + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => false; + public override long Length => calculatedLength; + + protected long _internalPosition; + } +} diff --git a/skyscraper8/ThreemediaOtt/ThreemediaContentFragment.cs b/skyscraper8/ThreemediaOtt/ThreemediaContentFragment.cs index a0fe450..16acb82 100644 --- a/skyscraper8/ThreemediaOtt/ThreemediaContentFragment.cs +++ b/skyscraper8/ThreemediaOtt/ThreemediaContentFragment.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using skyscraper5.Skyscraper.IO; using skyscraper8.Ietf.FLUTE; namespace skyscraper8.ThreemediaOtt @@ -52,5 +53,41 @@ namespace skyscraper8.ThreemediaOtt return true; } + + public ThreemediaContentFragmentStream CreateStream() + { + byte[] lastBlock = blocks[blocks.Length - 1]; + + MemoryStream lastBlockStream = new MemoryStream(lastBlock, false); + lastBlockStream.Position = lastBlockStream.Length - 8; + int footerLength = (int)lastBlockStream.ReadUInt32BE(); + int footerOffset = (int)(lastBlockStream.Length - 8 - footerLength); + + Dictionary footer = ExtractFooter(lastBlock, footerLength, footerOffset); + + int[] blockSizes = Array.ConvertAll(blocks, (x) => x.Length); + blockSizes[blocks.Length - 1] = footerOffset; + + ThreemediaContentFragmentStream result = new ThreemediaContentFragmentStream(blocks, blockSizes, footer); + + throw new NotImplementedException(); + } + + private Dictionary ExtractFooter(byte[] lastBlock, int footerLength, int footerOffset) + { + MemoryStream ms = new MemoryStream(lastBlock, footerOffset, footerLength, false); + StreamReader sr = new StreamReader(ms, Encoding.ASCII); + Dictionary result = new Dictionary(); + while (!sr.EndOfStream) + { + string? line = sr.ReadLine(); + int seperator = line.IndexOf(':'); + string key = line.Substring(0, seperator); + string value = line.Substring(seperator + 1); + result.Add(key, value); + } + + return result; + } } } diff --git a/skyscraper8/ThreemediaOtt/ThreemediaContentFragmentStream.cs b/skyscraper8/ThreemediaOtt/ThreemediaContentFragmentStream.cs new file mode 100644 index 0000000..35aef77 --- /dev/null +++ b/skyscraper8/ThreemediaOtt/ThreemediaContentFragmentStream.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.ThreemediaOtt +{ + internal class ThreemediaContentFragmentStream : ThreemediaBaseStream + { + private readonly byte[][] _blocks; + private readonly int[] _blockSizes; + + private int _currentBlockIndex; + private int _currentBlockPosition; + + public ThreemediaContentFragmentStream(byte[][] blocks, int[] blockSizes, Dictionary footer) + { + _blocks = blocks ?? throw new ArgumentNullException(nameof(blocks)); + _blockSizes = blockSizes ?? throw new ArgumentNullException(nameof(blockSizes)); + + if (_blocks.Length != _blockSizes.Length) + { + throw new ArgumentException("Blocks and blockSizes arrays must have the same length."); + } + + // Calculate total stream length based on the valid payloads + SetLength(_blockSizes.Sum(x => (long)x)); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + protected override void SeekHook() + { + long remainingOffset = _internalPosition; + _currentBlockIndex = 0; + _currentBlockPosition = 0; + + while (_currentBlockIndex < _blocks.Length && remainingOffset > 0) + { + int blockSize = _blockSizes[_currentBlockIndex]; + if (remainingOffset >= blockSize) + { + remainingOffset -= blockSize; + _currentBlockIndex++; + } + else + { + _currentBlockPosition = (int)remainingOffset; + remainingOffset = 0; + } + } + } + } +} diff --git a/skyscraper8/ThreemediaOtt/ThreemediaOttHandler.cs b/skyscraper8/ThreemediaOtt/ThreemediaOttHandler.cs index 338a050..0660e11 100644 --- a/skyscraper8/ThreemediaOtt/ThreemediaOttHandler.cs +++ b/skyscraper8/ThreemediaOtt/ThreemediaOttHandler.cs @@ -107,7 +107,12 @@ namespace skyscraper8.ThreemediaOtt return; } - throw new NotImplementedException(); + ThreemediaSessionStream stream = selectedSession.CreateStream(); + + FileStream fs = File.OpenWrite("test.bin"); + stream.CopyTo(fs); + fs.Flush(true); + fs.Close(); } public bool StopProcessingAfterThis() diff --git a/skyscraper8/ThreemediaOtt/ThreemediaSession.cs b/skyscraper8/ThreemediaOtt/ThreemediaSession.cs index 35c0c8f..222fd99 100644 --- a/skyscraper8/ThreemediaOtt/ThreemediaSession.cs +++ b/skyscraper8/ThreemediaOtt/ThreemediaSession.cs @@ -55,5 +55,12 @@ namespace skyscraper8.ThreemediaOtt fragments = null; } + + public ThreemediaSessionStream CreateStream() + { + ThreemediaContentFragmentStream[] fragmentStreams = Array.ConvertAll(fragments, (x) => x.CreateStream()); + ThreemediaSessionStream stream = new ThreemediaSessionStream(fragmentStreams); + return stream; + } } } diff --git a/skyscraper8/ThreemediaOtt/ThreemediaSessionStream.cs b/skyscraper8/ThreemediaOtt/ThreemediaSessionStream.cs new file mode 100644 index 0000000..c7115b0 --- /dev/null +++ b/skyscraper8/ThreemediaOtt/ThreemediaSessionStream.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace skyscraper8.ThreemediaOtt +{ + internal class ThreemediaSessionStream : ThreemediaBaseStream + { + public ThreemediaSessionStream(ThreemediaContentFragmentStream[] fragmentStreams) + { + throw new NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + protected override void SeekHook() + { + throw new NotImplementedException(); + } + } +}