Ejemplo n.º 1
0
        public Dictionary <int, Paragraph> GetTeletext(TeletextRunSettings teletextRunSettings, int pageNumber, int pageNumberBcd)
        {
            var timestamp = PresentationTimestamp.HasValue ? PresentationTimestamp.Value / 90 : 40;

            // do not allow timestamp to go back - treat lower timestamp as a reset/overflow
            var lastTimestamp = teletextRunSettings.GetLastTimestamp(pageNumber);

            teletextRunSettings.SetLastTimestamp(pageNumber, timestamp);
            timestamp += teletextRunSettings.GetAddTimestamp(pageNumber);
            if (lastTimestamp > 0 && lastTimestamp > timestamp)
            {
                teletextRunSettings.SetAddTimestamp(pageNumber, lastTimestamp);
            }

            // offset all time codes if first timestamp in ts file is > 1 sec
            timestamp = teletextRunSettings.SubtractStartMs(timestamp);

            if (timestamp < 40)
            {
                timestamp = 40; // Teletext.cs will subtract 40 ms (1 frame @25 fps) and this value must not be below 0
            }

            var teletextPages = new Dictionary <int, Paragraph>();
            var i             = 1;

            while (i <= _dataBuffer.Length - 6)
            {
                var dataUnitId  = _dataBuffer[i++];
                var dataUnitLen = _dataBuffer[i++];
                if (dataUnitId == (int)Teletext.DataUnitT.DataUnitEbuTeletextNonSubtitle || dataUnitId == (int)Teletext.DataUnitT.DataUnitEbuTeletextSubtitle)
                {
                    if (dataUnitLen == 44) // teletext payload has always size 44 bytes
                    {
                        Teletext.ProcessTelxPacket((Teletext.DataUnitT)dataUnitId, new Teletext.TeletextPacketPayload(_dataBuffer, i), timestamp, teletextRunSettings, pageNumberBcd, pageNumber);
                    }
                }
                i += dataUnitLen;
            }
            if (teletextRunSettings.PageNumberAndParagraph.ContainsKey(pageNumber) && teletextRunSettings.PageNumberAndParagraph[pageNumber] != null)
            {
                if (teletextPages.ContainsKey(pageNumber))
                {
                    teletextPages[pageNumber] = teletextRunSettings.PageNumberAndParagraph[pageNumber];
                }
                else
                {
                    teletextPages.Add(pageNumber, teletextRunSettings.PageNumberAndParagraph[pageNumber]);
                }
            }
            teletextRunSettings.PageNumberAndParagraph.Clear();
            return(teletextPages);
        }
Ejemplo n.º 2
0
        public Dictionary <int, Paragraph> GetTeletext(int packetId, TeletextRunSettings teletextRunSettings, int pageNumber, int pageNumberBcd, ulong?firstMs)
        {
            var lastTimestamp = PresentationTimestamp.HasValue ? PresentationTimestamp.Value / 90 : 40;

            if (firstMs.HasValue && lastTimestamp >= firstMs)
            {
                lastTimestamp -= firstMs.Value;
            }
            if (lastTimestamp < 40)
            {
                lastTimestamp = 40; // Teletext.cs will subtract 40 ms (1 frame @25 fps) and this value must not be below 0
            }
            var teletextPages = new Dictionary <int, Paragraph>();
            var i             = 1;

            while (i <= _dataBuffer.Length - 6)
            {
                var dataUnitId  = _dataBuffer[i++];
                var dataUnitLen = _dataBuffer[i++];
                if (dataUnitId == (int)Teletext.DataUnitT.DataUnitEbuTeletextNonSubtitle || dataUnitId == (int)Teletext.DataUnitT.DataUnitEbuTeletextSubtitle)
                {
                    if (dataUnitLen == 44) // teletext payload has always size 44 bytes
                    {
                        Teletext.ProcessTelxPacket((Teletext.DataUnitT)dataUnitId, new Teletext.TeletextPacketPayload(_dataBuffer, i), lastTimestamp, teletextRunSettings, pageNumberBcd, pageNumber);
                    }
                }
                i += dataUnitLen;
            }
            if (teletextRunSettings.PageNumberAndParagraph.ContainsKey(pageNumber) && teletextRunSettings.PageNumberAndParagraph[pageNumber] != null)
            {
                if (teletextPages.ContainsKey(pageNumber))
                {
                    teletextPages[pageNumber] = teletextRunSettings.PageNumberAndParagraph[pageNumber];
                }
                else
                {
                    teletextPages.Add(pageNumber, teletextRunSettings.PageNumberAndParagraph[pageNumber]);
                }
            }
            teletextRunSettings.PageNumberAndParagraph.Clear();
            return(teletextPages);
        }
Ejemplo n.º 3
0
        public static void ProcessTelxPacket(DataUnitT dataUnitId, TeletextPacketPayload packet, ulong timestamp, TeletextRunSettings teletextRunSettings, int targetPageNumberBcd, int targetPageNumberDec)
        {
            // variable names conform to ETS 300 706, chapter 7.1.2
            var address = (TeletextHamming.UnHamming84(packet.Address[1]) << 4) | TeletextHamming.UnHamming84(packet.Address[0]);
            var m       = address & 0x7;

            if (m == 0)
            {
                m = 8;
            }

            var y = (address >> 3) & 0x1f;
            var designationCode = y > 25 ? TeletextHamming.UnHamming84(packet.Data[0]) : 0x00;

            if (y == 0) // PAGE HEADER
            {
                // CC map
                var i            = (TeletextHamming.UnHamming84(packet.Data[1]) << 4) | TeletextHamming.UnHamming84(packet.Data[0]);
                var flagSubtitle = (TeletextHamming.UnHamming84(packet.Data[5]) & 0x08) >> 3;

                CcMap[i] |= (byte)(flagSubtitle << (m - 1));

                // Page number and control bits
                var pageNumber = (m << 8) | (TeletextHamming.UnHamming84(packet.Data[1]) << 4) | TeletextHamming.UnHamming84(packet.Data[0]);
                var charset    = ((TeletextHamming.UnHamming84(packet.Data[7]) & 0x08) | (TeletextHamming.UnHamming84(packet.Data[7]) & 0x04) | (TeletextHamming.UnHamming84(packet.Data[7]) & 0x02)) >> 1;
                //uint8_t flag_suppress_header = unham_8_4(packet.data[6]) & 0x01;
                //uint8_t flag_inhibit_display = (unham_8_4(packet.data[6]) & 0x08) >> 3;

                // ETS 300 706, chapter 9.3.1.3:
                // When set to '1' the service is designated to be in Serial mode and the transmission of a page is terminated
                // by the next page header with a different page number.
                // When set to '0' the service is designated to be in Parallel mode and the transmission of a page is terminated
                // by the next page header with a different page number but the same magazine number.
                // The same setting shall be used for all page headers in the service.
                // ETS 300 706, chapter 7.2.1: Page is terminated by and excludes the next page header packet
                // having the same magazine address in parallel transmission mode, or any magazine address in serial transmission mode.
                _transmissionMode = (TransmissionMode)(TeletextHamming.UnHamming84(packet.Data[7]) & 0x01);

                // FIXME: Well, this is not ETS 300 706 kosher, however we are interested in DATA_UNIT_EBU_TELETEXT_SUBTITLE only
                if (_transmissionMode == TransmissionMode.TransmissionModeParallel && dataUnitId != DataUnitT.DataUnitEbuTeletextSubtitle)
                {
                    return;
                }

                if (_receivingData &&
                    (_transmissionMode == TransmissionMode.TransmissionModeSerial && Page(pageNumber) != Page(targetPageNumberBcd) ||
                     _transmissionMode == TransmissionMode.TransmissionModeParallel && Page(pageNumber) != Page(targetPageNumberBcd) && m == Magazine(targetPageNumberBcd))
                    )
                {
                    _receivingData = false;
                    return;
                }

                // Page transmission is terminated, however now we are waiting for our new page
                if (targetPageNumberBcd != pageNumber)
                {
                    return;
                }

                // Now we have the beginning of page transmission; if there is page_buffer pending, process it
                if (_pageBuffer.Tainted)
                {
                    // it would be nice, if subtitle hides on previous video frame, so we contract 40 ms (1 frame @25 fps)
                    _pageBuffer.HideTimestamp = timestamp - 40;
                    ProcessPage(_pageBuffer, teletextRunSettings, targetPageNumberDec);
                }

                _pageBuffer.ShowTimestamp = timestamp;
                _pageBuffer.HideTimestamp = 0;
                _pageBuffer.Text          = new int[25, 40];
                _pageBuffer.Tainted       = false;
                _receivingData            = true;
                _primaryCharset.G0X28     = (int)BoolT.Undef;

                var c = _primaryCharset.G0M29 != (int)BoolT.Undef ? _primaryCharset.G0M29 : charset;
                RemapG0Charset(c);

                /*
                 * // I know -- not needed; in subtitles we will never need disturbing teletext page status bar
                 * // displaying tv station name, current time etc.
                 * if (flag_suppress_header == NO) {
                 *  for (uint8_t i = 14; i < 40; i++) page_buffer.text[y,i] = telx_to_ucs2(packet.data[i]);
                 *  //page_buffer.tainted = YES;
                 * }
                 */
            }
            else if (m == Magazine(targetPageNumberBcd) && y >= 1 && y <= 23 && _receivingData)
            {
                // ETS 300 706, chapter 9.4.1: Packets X/26 at presentation Levels 1.5, 2.5, 3.5 are used for addressing
                // a character location and overwriting the existing character defined on the Level 1 page
                // ETS 300 706, annex B.2.2: Packets with Y = 26 shall be transmitted before any packets with Y = 1 to Y = 25;
                // so page_buffer.text[y,i] may already contain any character received
                // in frame number 26, skip original G0 character
                for (var i = 0; i < 40; i++)
                {
                    if (_pageBuffer.Text[y, i] == 0x00)
                    {
                        _pageBuffer.Text[y, i] = TelxToUcs2(packet.Data[i]);
                    }
                }

                _pageBuffer.Tainted = true;
            }
            else if (m == Magazine(targetPageNumberBcd) && y == 26 && _receivingData)
            {
                // ETS 300 706, chapter 12.3.2: X/26 definition
                var x26Row = 0;

                var triplets = new uint[13];
                var j        = 0;
                for (var i = 1; i < 40; i += 3, j++)
                {
                    triplets[j] = TeletextHamming.UnHamming2418((packet.Data[i + 2] << 16) | (packet.Data[i + 1] << 8) | packet.Data[i]);
                }

                for (var j2 = 0; j2 < 13; j2++)
                {
                    if (triplets[j2] == 0xffffffff)
                    {
                        // invalid data (HAM24/18 uncorrectable error detected), skip group
                        _config.LogError($"! Unrecoverable data error; UNHAM24/18()={triplets[j2]}");
                        continue;
                    }

                    var data            = (triplets[j2] & 0x3f800) >> 11;
                    var mode            = (triplets[j2] & 0x7c0) >> 6;
                    var address2        = triplets[j2] & 0x3f;
                    var rowAddressGroup = address2 >= 40 && address2 <= 63;

                    // ETS 300 706, chapter 12.3.1, table 27: set active position
                    if (mode == 0x04 && rowAddressGroup)
                    {
                        x26Row = (int)(address2 - 40);
                        if (x26Row == 0)
                        {
                            x26Row = 24;
                        }
                    }

                    // ETS 300 706, chapter 12.3.1, table 27: termination marker
                    if (mode >= 0x11 && mode <= 0x1f && rowAddressGroup)
                    {
                        break;
                    }

                    // ETS 300 706, chapter 12.3.1, table 27: character from G2 set
                    int x26Col;
                    if (mode == 0x0f && !rowAddressGroup)
                    {
                        x26Col = (int)address2;
                        if (data > 31)
                        {
                            _pageBuffer.Text[x26Row, x26Col] = TeletextTables.G2[0, data - 0x20];
                        }
                    }

                    // ETS 300 706, chapter 12.3.1, table 27: G0 character with diacritical mark
                    if (mode >= 0x11 && mode <= 0x1f && !rowAddressGroup)
                    {
                        x26Col = (int)address2;
                        if (data >= 65 && data <= 90) // A-Z
                        {
                            _pageBuffer.Text[x26Row, x26Col] = TeletextTables.G2Accents[mode - 0x11, data - 65];
                        }
                        else if (data >= 97 && data <= 122) // a-z
                        {
                            _pageBuffer.Text[x26Row, x26Col] = TeletextTables.G2Accents[mode - 0x11, data - 71];
                        }
                        else // other
                        {
                            _pageBuffer.Text[x26Row, x26Col] = TelxToUcs2((byte)data);
                        }
                    }
                }
            }
            else if (m == Magazine(targetPageNumberBcd) && y == 28 && _receivingData)
            {
                // TODO:
                //   ETS 300 706, chapter 9.4.7: Packet X/28/4
                //   Where packets 28/0 and 28/4 are both transmitted as part of a page, packet 28/0 takes precedence over 28/4 for all but the color map entry coding.
                if (designationCode == 0 || designationCode == 4)
                {
                    // ETS 300 706, chapter 9.4.2: Packet X/28/0 Format 1
                    // ETS 300 706, chapter 9.4.7: Packet X/28/4
                    uint triplet0 = TeletextHamming.UnHamming2418((packet.Data[3] << 16) | (packet.Data[2] << 8) | packet.Data[1]);

                    if (triplet0 == 0xffffffff)
                    {
                        // invalid data (HAM24/18 uncorrectable error detected), skip group
                        _config.LogError($"! Unrecoverable data error; UNHAM24/18()={triplet0}");
                    }
                    else
                    {
                        // ETS 300 706, chapter 9.4.2: Packet X/28/0 Format 1 only
                        if ((triplet0 & 0x0f) == 0x00)
                        {
                            _primaryCharset.G0X28 = (int)((triplet0 & 0x3f80) >> 7);
                            RemapG0Charset(_primaryCharset.G0X28);
                        }
                    }
                }
            }
            else if (m == Magazine(targetPageNumberBcd) && y == 29)
            {
                // TODO:
                //   ETS 300 706, chapter 9.5.1 Packet M/29/0
                //   Where M/29/0 and M/29/4 are transmitted for the same magazine, M/29/0 takes precedence over M/29/4.
                if (designationCode == 0 || designationCode == 4)
                {
                    // ETS 300 706, chapter 9.5.1: Packet M/29/0
                    // ETS 300 706, chapter 9.5.3: Packet M/29/4
                    uint triplet0 = TeletextHamming.UnHamming2418((packet.Data[3] << 16) | (packet.Data[2] << 8) | packet.Data[1]);

                    if (triplet0 == 0xffffffff)
                    {
                        // invalid data (HAM24/18 uncorrectable error detected), skip group
                        _config.LogError($"! Unrecoverable data error; UNHAM24/18()={triplet0}");
                    }
                    else
                    {
                        // ETS 300 706, table 11: Coding of Packet M/29/0
                        // ETS 300 706, table 13: Coding of Packet M/29/4
                        if ((triplet0 & 0xff) == 0x00)
                        {
                            _primaryCharset.G0M29 = (int)((triplet0 & 0x3f80) >> 7);
                            // X/28 takes precedence over M/29
                            if (_primaryCharset.G0X28 == (int)BoolT.Undef)
                            {
                                RemapG0Charset(_primaryCharset.G0M29);
                            }
                        }
                    }
                }
            }
            else if (m == 8 && y == 30)
            {
                // ETS 300 706, chapter 9.8: Broadcast Service Data Packets
                if (!_states.ProgrammeInfoProcessed)
                {
                    // ETS 300 706, chapter 9.8.1: Packet 8/30 Format 1
                    if (TeletextHamming.UnHamming84(packet.Data[0]) < 2)
                    {
                        _config.LogInfoNoNewLine("- Programme Identification Data = ");
                        for (var i = 20; i < 40; i++)
                        {
                            var c = TelxToUcs2(packet.Data[i]);
                            // strip any control codes from PID, eg. TVP station
                            if (c < 0x20)
                            {
                                continue;
                            }
                            _config.LogInfoNoNewLine(Ucs2ToUtf8(c));
                        }
                        _config.LogInfo(string.Empty);

                        // OMG! ETS 300 706 stores timestamp in 7 bytes in Modified Julian Day in BCD format + HH:MM:SS in BCD format
                        // + timezone as 5-bit count of half-hours from GMT with 1-bit sign
                        // In addition all decimals are incremented by 1 before transmission.
                        long t = 0;
                        // 1st step: BCD to Modified Julian Day
                        t += (packet.Data[10] & 0x0f) * 10000;
                        t += ((packet.Data[11] & 0xf0) >> 4) * 1000;
                        t += (packet.Data[11] & 0x0f) * 100;
                        t += ((packet.Data[12] & 0xf0) >> 4) * 10;
                        t += packet.Data[12] & 0x0f;
                        t -= 11111;
                        // 2nd step: conversion Modified Julian Day to unix timestamp
                        t = (t - 40587) * 86400;
                        // 3rd step: add time
                        t += 3600 * (((packet.Data[13] & 0xf0) >> 4) * 10 + (packet.Data[13] & 0x0f));
                        t += 60 * (((packet.Data[14] & 0xf0) >> 4) * 10 + (packet.Data[14] & 0x0f));
                        t += ((packet.Data[15] & 0xf0) >> 4) * 10 + (packet.Data[15] & 0x0f);
                        t -= 40271;
                        // 4th step: conversion to time_t
                        var span      = TimeSpan.FromTicks(t * TimeSpan.TicksPerSecond);
                        var t2        = new DateTime(1970, 1, 1).Add(span);
                        var localTime = TimeZoneInfo.ConvertTimeFromUtc(t2, TimeZoneInfo.Local);

                        _config.LogInfo($"- Programme Timestamp (UTC) = {localTime.ToLongDateString()} {localTime.ToLongTimeString()}");
                        _config.LogInfo($"- Transmission mode = {(_transmissionMode == TransmissionMode.TransmissionModeSerial ? "serial" : "parallel")}");
                        _states.ProgrammeInfoProcessed = true;
                    }
                }
            }
        }
Ejemplo n.º 4
0
        static void ProcessPage(TeletextPage page, TeletextRunSettings teletextRunSettings, int pageNumber)
        {
#if DEBUG
            for (int row = 1; row < 25; row++)
            {
                _config.LogInfoNoNewLine($"# DEBUG[{row}]: ");
                for (int col = 0; col < 40; col++)
                {
                    _config.LogInfo($"{(page.Text[row, col]):X2} ");
                }
                _config.LogInfo(string.Empty);
            }
            _config.LogInfo(string.Empty);
#endif

            // optimization: slicing column by column -- higher probability we could find boxed area start mark sooner
            bool pageIsEmpty = true;
            for (var col = 0; col < 40; col++)
            {
                for (var row = 1; row < 25; row++)
                {
                    if (page.Text[row, col] == 0x0b)
                    {
                        pageIsEmpty = false;
                        break;
                    }
                }
            }
            if (pageIsEmpty)
            {
                return;
            }

            var paragraph = new Paragraph();
            var usedLines = new List <int>();
            var sb        = new StringBuilder();

            if (page.ShowTimestamp > page.HideTimestamp)
            {
                page.HideTimestamp = page.ShowTimestamp;
            }

            paragraph.StartTime = new TimeCode(page.ShowTimestamp);
            paragraph.EndTime   = new TimeCode(page.HideTimestamp);

            // process data
            for (var row = 1; row < 25; row++)
            {
                // anchors for string trimming purpose
                var  colStart = 40;
                var  colStop  = 40;
                bool boxOpen  = false;
                for (var col = 0; col < 40; col++)
                {
                    // replace all 0/B and 0/A characters with 0/20, as specified in ETS 300 706:
                    // Unless operating in "Hold Mosaics" mode, each character space occupied by a
                    // spacing attribute is displayed as a SPACE
                    if (page.Text[row, col] == 0xb) // open the box
                    {
                        if (colStart == 40)
                        {
                            colStart = col;
                        }
                        else
                        {
                            page.Text[row, col] = 0x20;
                        }
                        boxOpen = true;
                    }
                    else if (page.Text[row, col] == 0xa) // close the box
                    {
                        page.Text[row, col] = 0x20;
                        boxOpen             = false;
                    }
                    // characters between 0xA and 0xB shouldn't be displayed
                    // page->text[row][col] > 0x20 added to preserve color information
                    else if (!boxOpen && colStart < 40 && page.Text[row, col] > 0x20)
                    {
                        page.Text[row, col] = 0x20;
                    }
                }

                // line is empty
                if (colStart > 39)
                {
                    continue;
                }

                for (var col = colStart + 1; col <= 39; col++)
                {
                    if (page.Text[row, col] > 0x20)
                    {
                        if (colStop > 39)
                        {
                            colStart = col;
                        }
                        colStop = col;
                    }
                    if (page.Text[row, col] == 0xa)
                    {
                        break;
                    }
                }
                // line is empty
                if (colStop > 39)
                {
                    continue;
                }

                // ETS 300 706, chapter 12.2: Alpha White ("Set-After") - Start-of-row default condition.
                // used for color changes _before_ start box mark
                // white is default as stated in ETS 300 706, chapter 12.2
                // black(0), red(1), green(2), yellow(3), blue(4), magenta(5), cyan(6), white(7)
                var  foregroundColor = 0x7;
                bool fontTagOpened   = false;

                for (var col = 0; col <= colStop; col++)
                {
                    // v is just a shortcut
                    var v = page.Text[row, col];

                    if (col < colStart)
                    {
                        if (v <= 0x7)
                        {
                            foregroundColor = v;
                        }
                    }

                    if (col == colStart)
                    {
                        if (foregroundColor != 0x7 && _config.Colors)
                        {
                            sb.Append($"<font color=\"{TeletextColors[foregroundColor]}\">");
                            fontTagOpened = true;
                        }
                    }

                    if (col >= colStart)
                    {
                        if (v <= 0x7)
                        {
                            // ETS 300 706, chapter 12.2: Unless operating in "Hold Mosaics" mode,
                            // each character space occupied by a spacing attribute is displayed as a SPACE.
                            if (_config.Colors)
                            {
                                if (fontTagOpened)
                                {
                                    sb.Append("</font> ");
                                    fontTagOpened = false;
                                }

                                // black is considered as white for telxcc purpose
                                // telxcc writes <font/> tags only when needed
                                if (v > 0x0 && v < 0x7)
                                {
                                    sb.Append($"<font color=\"{TeletextColors[v]}\">");
                                    fontTagOpened = true;
                                }
                            }
                            else
                            {
                                v = 0x20;
                            }
                        }

                        if (v >= 0x20)
                        {
                            sb.Append(Ucs2ToUtf8(v));
                        }
                    }
                }

                // no tag will left opened!
                if (_config.Colors && fontTagOpened)
                {
                    sb.Append("</font>");
                }

                // line delimiter
                sb.Append(Environment.NewLine);
                usedLines.Add(row);
            }
            sb.AppendLine();
            var topAlign = usedLines.Count > 0 && usedLines.All(p => p < 6);
            paragraph.Text = (topAlign ? "{\\an8}" : "") + sb.ToString().TrimEnd();
            if (!teletextRunSettings.PageNumberAndParagraph.ContainsKey(pageNumber))
            {
                teletextRunSettings.PageNumberAndParagraph.Add(pageNumber, paragraph);
            }
            else
            {
                _config.LogError("New pageId in page: " + pageNumber);
            }
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Can be used with e.g. MemoryStream or FileStream
        /// </summary>
        /// <param name="ms">Input stream</param>
        /// <param name="callback">Optional callback event to follow progress</param>
        public void Parse(Stream ms, LoadTransportStreamCallback callback)
        {
            IsM2TransportStream         = false;
            NumberOfNullPackets         = 0;
            TotalNumberOfPackets        = 0;
            TotalNumberOfPrivateStream1 = 0;
            TotalNumberOfPrivateStream1Continuation0 = 0;
            SubtitlePacketIds = new List <int>();
            SubtitlePackets   = new List <Packet>();
            ms.Position       = 0;
            const int packetLength = 188;

            IsM2TransportStream = IsM3TransportStream(ms);
            var  packetBuffer       = new byte[packetLength];
            var  m2TsTimeCodeBuffer = new byte[4];
            long position           = 0;

            SubtitlesLookup         = new SortedDictionary <int, List <DvbSubPes> >();
            TeletextSubtitlesLookup = new SortedDictionary <int, SortedDictionary <int, List <Paragraph> > >();
            var   teletextPesList = new Dictionary <int, List <DvbSubPes> >();
            var   teletextPages   = new Dictionary <int, List <int> >();
            ulong?firstMs         = null;
            ulong?firstVideoMs    = null;

            // check for Topfield .rec file
            ms.Seek(position, SeekOrigin.Begin);
            ms.Read(m2TsTimeCodeBuffer, 0, 3);
            if (m2TsTimeCodeBuffer[0] == 0x54 && m2TsTimeCodeBuffer[1] == 0x46 && m2TsTimeCodeBuffer[2] == 0x72)
            {
                position = 3760;
            }

            long transportStreamLength = ms.Length;

            ms.Seek(position, SeekOrigin.Begin);
            while (position < transportStreamLength)
            {
                if (IsM2TransportStream)
                {
                    ms.Read(m2TsTimeCodeBuffer, 0, m2TsTimeCodeBuffer.Length);
                    position += m2TsTimeCodeBuffer.Length;
                }

                var bytesRead = ms.Read(packetBuffer, 0, packetLength);
                if (bytesRead < packetLength)
                {
                    break; // incomplete packet at end-of-file
                }

                if (packetBuffer[0] == Packet.SynchronizationByte)
                {
                    var packet = new Packet(packetBuffer);
                    if (packet.IsNullPacket)
                    {
                        NumberOfNullPackets++;
                    }

                    else if (!firstVideoMs.HasValue && packet.IsVideoStream)
                    {
                        if (packet.Payload != null && packet.Payload.Length > 10)
                        {
                            int presentationTimestampDecodeTimestampFlags = packet.Payload[7] >> 6;
                            if (presentationTimestampDecodeTimestampFlags == Helper.B00000010 ||
                                presentationTimestampDecodeTimestampFlags == Helper.B00000011)
                            {
                                firstVideoMs  = (ulong)packet.Payload[9 + 4] >> 1;
                                firstVideoMs += (ulong)packet.Payload[9 + 3] << 7;
                                firstVideoMs += (ulong)(packet.Payload[9 + 2] & Helper.B11111110) << 14;
                                firstVideoMs += (ulong)packet.Payload[9 + 1] << 22;
                                firstVideoMs += (ulong)(packet.Payload[9 + 0] & Helper.B00001110) << 29;
                                firstVideoMs  = firstVideoMs / 90;
                            }
                        }
                    }

                    else if (packet.IsPrivateStream1 || SubtitlePacketIds.Contains(packet.PacketId))
                    {
                        TotalNumberOfPrivateStream1++;

                        if (!SubtitlePacketIds.Contains(packet.PacketId))
                        {
                            SubtitlePacketIds.Add(packet.PacketId);
                        }

                        if (packet.PayloadUnitStartIndicator)
                        {
                            firstMs = ProcessPackages(packet.PacketId, teletextPages, teletextPesList, firstMs);
                            SubtitlePackets.RemoveAll(p => p.PacketId == packet.PacketId);
                        }
                        SubtitlePackets.Add(packet);

                        if (packet.ContinuityCounter == 0)
                        {
                            TotalNumberOfPrivateStream1Continuation0++;
                        }
                    }
                    TotalNumberOfPackets++;
                    position += packetLength;

                    if (TotalNumberOfPackets % 100000 == 0)
                    {
                        callback?.Invoke(position, transportStreamLength);
                    }
                }
                else
                {
                    // sync byte not found - search for it (will be very slow!)
                    if (IsM2TransportStream)
                    {
                        position -= m2TsTimeCodeBuffer.Length;
                    }
                    position++;
                    ms.Seek(position, SeekOrigin.Begin);
                }
            }
            foreach (var pid in SubtitlePackets.GroupBy(p => p.PacketId))
            {
                firstMs = ProcessPackages(pid.Key, teletextPages, teletextPesList, firstMs);
            }
            SubtitlePackets.Clear();
            callback?.Invoke(transportStreamLength, transportStreamLength);


            foreach (var packetId in teletextPesList.Keys) // teletext from PES packets
            {
                foreach (var page in teletextPages[packetId].OrderBy(p => p))
                {
                    var pageBcd = Teletext.DecToBec(page);
                    Teletext.InitializeStaticFields(packetId, pageBcd);
                    var teletextRunSettings = new TeletextRunSettings();
                    foreach (var pes in teletextPesList[packetId])
                    {
                        var textDictionary = pes.GetTeletext(packetId, teletextRunSettings, page, pageBcd, firstMs);
                        foreach (var dic in textDictionary)
                        {
                            if (!string.IsNullOrEmpty(dic.Value.Text))
                            {
                                if (TeletextSubtitlesLookup.ContainsKey(packetId))
                                {
                                    var innerDic = TeletextSubtitlesLookup[packetId];
                                    if (innerDic.ContainsKey(dic.Key))
                                    {
                                        innerDic[dic.Key].Add(dic.Value);
                                    }
                                    else
                                    {
                                        innerDic.Add(dic.Key, new List <Paragraph> {
                                            dic.Value
                                        });
                                    }
                                }
                                else
                                {
                                    TeletextSubtitlesLookup.Add(packetId, new SortedDictionary <int, List <Paragraph> > {
                                        { dic.Key, new List <Paragraph> {
                                              dic.Value
                                          } }
                                    });
                                }
                            }
                        }
                    }
                }
            }

            if (IsM2TransportStream) // m2ts blu-ray images from PES packets
            {
                DvbSubtitlesLookup = new SortedDictionary <int, List <TransportStreamSubtitle> >();
                foreach (int pid in SubtitlesLookup.Keys)
                {
                    var bdMs        = new MemoryStream();
                    var list        = SubtitlesLookup[pid];
                    var currentList = new List <DvbSubPes>();
                    var sb          = new StringBuilder();
                    var subList     = new List <TransportStreamSubtitle>();
                    var offset      = (long)(firstVideoMs ?? 0); // when to use firstMs ?
                    for (var index = 0; index < list.Count; index++)
                    {
                        var item = list[index];
                        item.WriteToStream(bdMs);
                        currentList.Add(item);
                        if (item.DataIdentifier == 0x80)
                        {
                            bdMs.Position = 0;
                            var bdList = BluRaySupParser.ParseBluRaySup(bdMs, sb, true);
                            if (bdList.Count > 0)
                            {
                                var startMs = currentList.First().PresentationTimestampToMilliseconds();
                                var endMs   = index + 1 < list.Count ? list[index + 1].PresentationTimestampToMilliseconds() : startMs + (ulong)Configuration.Settings.General.NewEmptyDefaultMs;
                                startMs = (ulong)((long)startMs - offset);
                                endMs   = (ulong)((long)endMs - offset);
                                subList.Add(new TransportStreamSubtitle(bdList[0], startMs, endMs));
                            }
                            bdMs.Dispose();
                            bdMs = new MemoryStream();
                            currentList.Clear();
                        }
                    }

                    if (subList.Count > 0)
                    {
                        DvbSubtitlesLookup.Add(pid, subList);
                    }
                }
                SubtitlePacketIds.Clear();
                foreach (int key in DvbSubtitlesLookup.Keys)
                {
                    SubtitlePacketIds.Add(key);
                }
                return;
            }

            SubtitlePacketIds.Clear();
            foreach (int key in SubtitlesLookup.Keys)
            {
                SubtitlePacketIds.Add(key);
            }
            SubtitlePacketIds.Sort();

            // Merge packets and set start/end time
            DvbSubtitlesLookup = new SortedDictionary <int, List <TransportStreamSubtitle> >();
            foreach (int pid in SubtitlePacketIds)
            {
                var subtitles = new List <TransportStreamSubtitle>();
                var list      = ParseAndRemoveEmpty(GetSubtitlePesPackets(pid));
                var offset    = (long)(firstVideoMs ?? 0); // when to use firstMs ?
                for (int i = 0; i < list.Count; i++)
                {
                    var pes = list[i];
                    pes.ParseSegments();
                    if (pes.ObjectDataList.Count > 0)
                    {
                        var sub = new TransportStreamSubtitle {
                            StartMilliseconds = pes.PresentationTimestampToMilliseconds(), Pes = pes
                        };
                        if (i + 1 < list.Count && list[i + 1].PresentationTimestampToMilliseconds() > 25)
                        {
                            sub.EndMilliseconds = list[i + 1].PresentationTimestampToMilliseconds() - 25;
                        }
                        if (sub.EndMilliseconds < sub.StartMilliseconds || sub.EndMilliseconds - sub.StartMilliseconds > (ulong)Configuration.Settings.General.SubtitleMaximumDisplayMilliseconds)
                        {
                            sub.EndMilliseconds = sub.StartMilliseconds + (ulong)Configuration.Settings.General.SubtitleMaximumDisplayMilliseconds;
                        }

                        if (offset <= (long)sub.StartMilliseconds || offset < 0)
                        {
                            sub.StartMilliseconds = (ulong)((long)sub.StartMilliseconds - offset);
                            sub.EndMilliseconds   = (ulong)((long)sub.EndMilliseconds - offset);
                        }
                        else
                        {
                            if (subtitles.Count > 0)
                            {
                                offset = (long)sub.StartMilliseconds - (long)subtitles[subtitles.Count - 1].EndMilliseconds + 1000;
                                sub.StartMilliseconds = (ulong)((long)sub.StartMilliseconds - offset);
                                sub.EndMilliseconds   = (ulong)((long)sub.EndMilliseconds - offset);
                            }
                        }
                        subtitles.Add(sub);
                    }
                }
                DvbSubtitlesLookup.Add(pid, subtitles);
            }
            SubtitlePacketIds.Clear();
            foreach (int key in DvbSubtitlesLookup.Keys)
            {
                if (DvbSubtitlesLookup[key].Count > 0)
                {
                    SubtitlePacketIds.Add(key);
                }
            }
            SubtitlePacketIds.Sort();
        }