2015-09-06 19:57:33 +02:00
|
|
|
#include "./oggiterator.h"
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2015-09-06 19:57:33 +02:00
|
|
|
#include "../exceptions.h"
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2017-08-29 01:29:27 +02:00
|
|
|
#include <c++utilities/io/binaryreader.h>
|
|
|
|
|
2016-03-22 22:52:36 +01:00
|
|
|
#include <iostream>
|
2017-08-29 01:29:27 +02:00
|
|
|
#include <limits>
|
2016-03-22 22:52:36 +01:00
|
|
|
|
|
|
|
using namespace std;
|
2019-06-10 22:49:11 +02:00
|
|
|
using namespace CppUtilities;
|
2016-03-22 22:52:36 +01:00
|
|
|
|
2018-03-06 23:09:15 +01:00
|
|
|
namespace TagParser {
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
/*!
|
2018-06-03 20:38:32 +02:00
|
|
|
* \class TagParser::OggIterator
|
2015-04-22 19:22:01 +02:00
|
|
|
* \brief The OggIterator class helps iterating through all segments of an OGG bitstream.
|
|
|
|
*
|
|
|
|
* If an OggIterator has just been constructed it is invalid. To fetch the first page from
|
|
|
|
* the stream call the reset() method. The iterator will now point to the first segment of the
|
|
|
|
* first page.
|
|
|
|
*
|
|
|
|
* To go on call the appropriate methods. Parsing exceptions and IO exceptions might occur during iteration.
|
|
|
|
*
|
|
|
|
* The internal buffer of OGG pages might be accessed using the pages() method.
|
|
|
|
*/
|
|
|
|
|
2016-03-22 22:52:36 +01:00
|
|
|
/*!
|
|
|
|
* \brief Sets the stream and related parameters and clears all available pages.
|
|
|
|
* \remarks Invalidates the iterator. Use reset() to continue iteration.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
void OggIterator::clear(istream &stream, std::uint64_t startOffset, std::uint64_t streamSize)
|
2016-03-22 22:52:36 +01:00
|
|
|
{
|
|
|
|
m_stream = &stream;
|
|
|
|
m_startOffset = startOffset;
|
|
|
|
m_streamSize = streamSize;
|
|
|
|
m_pages.clear();
|
|
|
|
}
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
/*!
|
|
|
|
* \brief Resets the iterator to point at the first segment of the first page (matching the filter if set).
|
|
|
|
*
|
2021-07-02 03:00:50 +02:00
|
|
|
* Fetched pages (directly accessible through the page() method) remain after resetting the iterator. Use
|
2016-12-27 23:40:36 +01:00
|
|
|
* clear() to clear all pages.
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
|
|
|
void OggIterator::reset()
|
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
for (m_page = m_segment = m_offset = 0; m_page < m_pages.size() || fetchNextPage(); ++m_page) {
|
2015-04-22 19:22:01 +02:00
|
|
|
const OggPage &page = m_pages[m_page];
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!page.segmentSizes().empty() && matchesFilter(page)) {
|
2015-04-22 19:22:01 +02:00
|
|
|
// page is not empty and matches ID filter if set
|
|
|
|
m_offset = page.startOffset() + page.headerSize();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// no matching page found -> iterator is invalid
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-03-22 22:52:36 +01:00
|
|
|
* \brief Increases the current position by one page.
|
|
|
|
* \remarks The iterator must be valid. The iterator might be invalidated.
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
|
|
|
void OggIterator::nextPage()
|
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
while (++m_page < m_pages.size() || fetchNextPage()) {
|
2016-03-22 22:52:36 +01:00
|
|
|
const OggPage &page = m_pages[m_page];
|
2018-03-07 01:17:50 +01:00
|
|
|
if (!page.segmentSizes().empty() && matchesFilter(page)) {
|
2016-03-22 22:52:36 +01:00
|
|
|
// page is not empty and matches ID filter if set
|
|
|
|
m_segment = m_bytesRead = 0;
|
|
|
|
m_offset = page.startOffset() + page.headerSize();
|
|
|
|
return;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
2016-03-22 22:52:36 +01:00
|
|
|
// no next page available -> iterator is in invalid state
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-03-22 22:52:36 +01:00
|
|
|
* \brief Increases the current position by one segment.
|
|
|
|
* \remarks The iterator must be valid. The iterator might be invalidated.
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
|
|
|
void OggIterator::nextSegment()
|
|
|
|
{
|
2016-03-22 22:52:36 +01:00
|
|
|
const OggPage &page = m_pages[m_page];
|
2018-03-07 01:17:50 +01:00
|
|
|
if (matchesFilter(page) && ++m_segment < page.segmentSizes().size()) {
|
2016-03-22 22:52:36 +01:00
|
|
|
// current page has next segment
|
|
|
|
m_bytesRead = 0;
|
|
|
|
m_offset += page.segmentSizes()[m_segment - 1];
|
|
|
|
} else {
|
|
|
|
// next (matching) page has next segment
|
|
|
|
nextPage();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-03-22 22:52:36 +01:00
|
|
|
* \brief Decreases the current position by one page.
|
|
|
|
* \remarks The iterator must be valid. The iterator might be invalidated.
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
|
|
|
void OggIterator::previousPage()
|
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
while (m_page) {
|
2016-03-22 22:52:36 +01:00
|
|
|
const OggPage &page = m_pages[--m_page];
|
2018-03-07 01:17:50 +01:00
|
|
|
if (matchesFilter(page)) {
|
2016-03-22 22:52:36 +01:00
|
|
|
m_offset = page.dataOffset(m_segment = page.segmentSizes().size() - 1);
|
|
|
|
return;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2016-03-22 22:52:36 +01:00
|
|
|
* \brief Decreases the current position by one segment.
|
|
|
|
* \remarks The iterator must be valid. The iterator might be invalidated.
|
2015-04-22 19:22:01 +02:00
|
|
|
*/
|
|
|
|
void OggIterator::previousSegment()
|
|
|
|
{
|
2016-03-22 22:52:36 +01:00
|
|
|
const OggPage &page = m_pages[m_page];
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_segment && matchesFilter(page)) {
|
2016-03-22 22:52:36 +01:00
|
|
|
m_offset -= page.segmentSizes()[m_segment--];
|
|
|
|
} else {
|
|
|
|
previousPage();
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Reads \a count bytes from the OGG stream and writes it to the specified \a buffer.
|
|
|
|
* \remarks
|
|
|
|
* - Might increase the current page index and/or the current segment index.
|
|
|
|
* - Page headers are skipped (this is the whole purpose of this method).
|
|
|
|
* \throws Throws a TruncatedDataException if the end of the stream is reached before \a count bytes
|
|
|
|
* have been read.
|
2016-05-16 20:56:53 +02:00
|
|
|
* \sa readAll()
|
2015-04-22 19:22:01 +02:00
|
|
|
* \sa currentCharacterOffset()
|
|
|
|
* \sa seekForward()
|
|
|
|
*/
|
2021-03-20 21:26:25 +01:00
|
|
|
void OggIterator::read(char *buffer, std::size_t count)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2021-03-20 21:26:25 +01:00
|
|
|
std::size_t bytesRead = 0;
|
2018-03-07 01:17:50 +01:00
|
|
|
while (*this && count) {
|
2021-08-15 23:33:50 +02:00
|
|
|
const auto available = remainingBytesInCurrentSegment();
|
2021-03-20 21:26:25 +01:00
|
|
|
stream().seekg(static_cast<std::streamoff>(currentCharacterOffset()));
|
2018-03-07 01:17:50 +01:00
|
|
|
if (count <= available) {
|
2021-03-20 21:26:25 +01:00
|
|
|
stream().read(buffer + bytesRead, static_cast<std::streamsize>(count));
|
2015-04-22 19:22:01 +02:00
|
|
|
m_bytesRead += count;
|
|
|
|
return;
|
|
|
|
}
|
2021-03-20 21:26:25 +01:00
|
|
|
stream().read(buffer + bytesRead, static_cast<std::streamsize>(available));
|
2018-08-12 22:14:21 +02:00
|
|
|
nextSegment();
|
|
|
|
bytesRead += available;
|
|
|
|
count -= available;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (count) {
|
2016-03-22 22:52:36 +01:00
|
|
|
// still bytes to read but no more available
|
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
|
2016-05-16 20:56:53 +02:00
|
|
|
/*!
|
|
|
|
* \brief Reads all bytes from the OGG stream and writes it to the specified \a buffer.
|
|
|
|
* \remarks
|
|
|
|
* - Might increase the current page index and/or the current segment index.
|
|
|
|
* - Page headers are skipped (this is the whole purpose of this method).
|
2017-08-29 01:29:27 +02:00
|
|
|
* - Does not write more than \a max bytes to the buffer.
|
|
|
|
* \returns Returns the number of bytes read from the OGG stream. This might be less than \a max in
|
|
|
|
* case not that many bytes were available.
|
2016-05-16 20:56:53 +02:00
|
|
|
* \sa read()
|
|
|
|
* \sa currentCharacterOffset()
|
|
|
|
* \sa seekForward()
|
2021-08-16 18:44:28 +02:00
|
|
|
* \deprecated Remove this unused function in v11.
|
2016-05-16 20:56:53 +02:00
|
|
|
*/
|
2021-03-20 21:26:25 +01:00
|
|
|
std::size_t OggIterator::readAll(char *buffer, std::size_t max)
|
2016-05-16 20:56:53 +02:00
|
|
|
{
|
2021-03-20 21:26:25 +01:00
|
|
|
auto bytesRead = std::size_t(0);
|
2018-03-07 01:17:50 +01:00
|
|
|
while (*this && max) {
|
2021-08-15 23:33:50 +02:00
|
|
|
const auto available = remainingBytesInCurrentSegment();
|
2021-03-20 21:26:25 +01:00
|
|
|
stream().seekg(static_cast<std::streamoff>(currentCharacterOffset()), std::ios_base::beg);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (max <= available) {
|
2021-03-20 21:26:25 +01:00
|
|
|
stream().read(buffer + bytesRead, static_cast<std::streamsize>(max));
|
2016-05-16 20:56:53 +02:00
|
|
|
m_bytesRead += max;
|
|
|
|
return bytesRead + max;
|
|
|
|
} else {
|
2021-03-20 21:26:25 +01:00
|
|
|
stream().read(buffer + bytesRead, static_cast<std::streamsize>(available));
|
2016-05-16 20:56:53 +02:00
|
|
|
nextSegment();
|
|
|
|
bytesRead += available;
|
|
|
|
max -= available;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return bytesRead;
|
|
|
|
}
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
/*!
|
|
|
|
* \brief Advances the position of the next character to be read from the OGG stream by \a count bytes.
|
|
|
|
* \remarks
|
|
|
|
* - Might increase the current page index and/or the current segment index.
|
|
|
|
* - Page headers are skipped (this is the whole purpose of this method).
|
|
|
|
* - Seeking backward is not implemented yet since there is currently no use for such a method.
|
|
|
|
* \throws Throws a TruncatedDataException if the end of the stream is exceeded.
|
|
|
|
* \sa currentCharacterOffset()
|
|
|
|
* \sa read()
|
|
|
|
*/
|
2021-03-20 21:26:25 +01:00
|
|
|
void OggIterator::ignore(std::size_t count)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
while (*this) {
|
2021-08-13 20:08:40 +02:00
|
|
|
const auto available = currentSegmentSize() - m_bytesRead;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (count <= available) {
|
2015-04-22 19:22:01 +02:00
|
|
|
m_bytesRead += count;
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
nextSegment();
|
|
|
|
count -= available;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw TruncatedDataException();
|
|
|
|
}
|
|
|
|
|
2017-08-29 01:29:27 +02:00
|
|
|
/*!
|
|
|
|
* \brief Fetches the next page at the specified \a offset.
|
|
|
|
*
|
|
|
|
* This allows to omit parts of a file which is useful to
|
|
|
|
* - find the last page faster by skipping pages in the middle (last page is required for calculating
|
|
|
|
* the files duration).
|
2018-07-23 14:44:06 +02:00
|
|
|
* - recover parsing after after an error occurred.
|
2017-08-29 01:29:27 +02:00
|
|
|
*
|
|
|
|
* Regardless of the current iterator position, this method will assume the page at \a offset comes after
|
2021-07-02 03:00:50 +02:00
|
|
|
* the last known page. Hence \a offset must be greater than OggPage::startOffset() + OggPage::totalSize() of the
|
2017-08-29 01:29:27 +02:00
|
|
|
* last known page. This is checked by the method.
|
|
|
|
*
|
|
|
|
* If the OGG capture pattern is not present at \a offset, up to 65307 bytes (max. size of an OGG page) are
|
|
|
|
* skipped. So in a valid stream, this method will always succeed if \a offset is less than the stream size minus
|
|
|
|
* 65307.
|
|
|
|
*
|
|
|
|
* If a page could be found, it is appended to pages() and the iterator position is set to the first segment of
|
|
|
|
* that page. If no page could be found, this method does not alter the iterator.
|
|
|
|
*
|
|
|
|
* \returns Returns an indication whether a page could be found.
|
|
|
|
* \throws Throws std::ios_base::failure when an IO error occurs.
|
|
|
|
* \throws Throws Failure when a parsing error occurs.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
bool OggIterator::resyncAt(std::uint64_t offset)
|
2017-08-29 01:29:27 +02:00
|
|
|
{
|
|
|
|
// check whether offset is valid
|
2018-03-07 01:17:50 +01:00
|
|
|
if (offset >= streamSize() || offset < (m_pages.empty() ? m_startOffset : m_pages.back().startOffset() + m_pages.back().totalSize())) {
|
2017-08-29 01:29:27 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// find capture pattern 'OggS'
|
2018-06-02 22:56:08 +02:00
|
|
|
stream().seekg(static_cast<streamoff>(offset));
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t lettersFound = 0;
|
|
|
|
for (std::uint64_t bytesAvailable = max<std::uint64_t>(streamSize() - offset, 65307ul); bytesAvailable >= 27; --bytesAvailable) {
|
2018-03-07 01:17:50 +01:00
|
|
|
switch (static_cast<char>(stream().get())) {
|
2017-08-29 01:29:27 +02:00
|
|
|
case 'O':
|
|
|
|
lettersFound = 1;
|
|
|
|
break;
|
|
|
|
case 'g':
|
|
|
|
lettersFound = lettersFound == 1 || lettersFound == 2 ? lettersFound + 1 : 0;
|
|
|
|
break;
|
|
|
|
case 'S':
|
2018-03-07 01:17:50 +01:00
|
|
|
if (lettersFound == 3) {
|
2017-08-29 01:29:27 +02:00
|
|
|
// capture pattern found
|
|
|
|
const auto currentOffset = stream().tellg();
|
|
|
|
// -> try to parse an OGG page at this position
|
|
|
|
try {
|
2019-03-13 19:06:42 +01:00
|
|
|
m_pages.emplace_back(stream(), static_cast<std::uint64_t>(stream().tellg()) - 4,
|
|
|
|
bytesAvailable > numeric_limits<std::int32_t>::max() ? numeric_limits<std::int32_t>::max()
|
|
|
|
: static_cast<std::int32_t>(bytesAvailable));
|
2017-08-29 01:29:27 +02:00
|
|
|
setPageIndex(m_pages.size() - 1);
|
|
|
|
return true;
|
|
|
|
} catch (const Failure &) {
|
|
|
|
stream().seekg(currentOffset);
|
|
|
|
}
|
|
|
|
}
|
2019-06-12 20:40:45 +02:00
|
|
|
[[fallthrough]];
|
2017-08-29 01:29:27 +02:00
|
|
|
default:
|
|
|
|
lettersFound = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-04-22 19:22:01 +02:00
|
|
|
/*!
|
|
|
|
* \brief Fetches the next page.
|
|
|
|
*
|
|
|
|
* A new page can only be fetched if the current page is the last page in the buffer and if the
|
|
|
|
* end of the input stream has not been reached yet.
|
|
|
|
*
|
|
|
|
* \returns Returns an indication whether the next page could be fetched.
|
|
|
|
* \throws Throws std::ios_base::failure when an IO error occurs.
|
|
|
|
* \throws Throws Failure when a parsing error occurs.
|
|
|
|
*/
|
|
|
|
bool OggIterator::fetchNextPage()
|
|
|
|
{
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_page == m_pages.size()) { // can only fetch the next page if the current page is the last page
|
2015-04-22 19:22:01 +02:00
|
|
|
m_offset = m_pages.empty() ? m_startOffset : m_pages.back().startOffset() + m_pages.back().totalSize();
|
2018-03-07 01:17:50 +01:00
|
|
|
if (m_offset < m_streamSize) {
|
2019-03-13 19:06:42 +01:00
|
|
|
const std::uint64_t bytesAvailable = m_streamSize - m_offset;
|
2018-03-07 01:17:50 +01:00
|
|
|
m_pages.emplace_back(*m_stream, m_offset,
|
2019-03-13 19:06:42 +01:00
|
|
|
bytesAvailable > numeric_limits<std::int32_t>::max() ? numeric_limits<std::int32_t>::max()
|
|
|
|
: static_cast<std::int32_t>(bytesAvailable));
|
2015-04-22 19:22:01 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-03-07 01:17:50 +01:00
|
|
|
} // namespace TagParser
|