/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "TimedTextParser.h" #include namespace android { TimedTextParser::TimedTextParser() : mDataSource(NULL), mOffset(0), mIndex(0) { } TimedTextParser::~TimedTextParser() { reset(); } status_t TimedTextParser::init( const sp &dataSource, FileType fileType) { mDataSource = dataSource; mFileType = fileType; status_t err; if ((err = scanFile()) != OK) { reset(); return err; } return OK; } void TimedTextParser::reset() { mDataSource.clear(); mTextVector.clear(); mOffset = 0; mIndex = 0; } // scan the text file to get start/stop time and the // offset of each piece of text content status_t TimedTextParser::scanFile() { if (mFileType != OUT_OF_BAND_FILE_SRT) { return ERROR_UNSUPPORTED; } off64_t offset = 0; int64_t startTimeUs; bool endOfFile = false; while (!endOfFile) { TextInfo info; status_t err = getNextInSrtFileFormat(&offset, &startTimeUs, &info); if (err != OK) { if (err == ERROR_END_OF_STREAM) { endOfFile = true; } else { return err; } } else { mTextVector.add(startTimeUs, info); } } if (mTextVector.isEmpty()) { return ERROR_MALFORMED; } return OK; } // read one line started from *offset and store it into data. status_t TimedTextParser::readNextLine(off64_t *offset, AString *data) { char character; data->clear(); while (true) { ssize_t err; if ((err = mDataSource->readAt(*offset, &character, 1)) < 1) { if (err == 0) { return ERROR_END_OF_STREAM; } return ERROR_IO; } (*offset) ++; // a line could end with CR, LF or CR + LF if (character == 10) { break; } else if (character == 13) { if ((err = mDataSource->readAt(*offset, &character, 1)) < 1) { if (err == 0) { // end of the stream return OK; } return ERROR_IO; } (*offset) ++; if (character != 10) { (*offset) --; } break; } data->append(character); } return OK; } /* SRT format: * Subtitle number * Start time --> End time * Text of subtitle (one or more lines) * Blank lines * * .srt file example: * 1 * 00:00:20,000 --> 00:00:24,400 * Altocumulus clouds occur between six thousand * * 2 * 00:00:24,600 --> 00:00:27,800 * and twenty thousand feet above ground level. */ status_t TimedTextParser::getNextInSrtFileFormat( off64_t *offset, int64_t *startTimeUs, TextInfo *info) { AString data; status_t err; // To skip blank lines. do { if ((err = readNextLine(offset, &data)) != OK) { return err; } data.trim(); } while(data.empty()); // Just ignore the first non-blank line which is subtitle sequence number. if ((err = readNextLine(offset, &data)) != OK) { return err; } int hour1, hour2, min1, min2, sec1, sec2, msec1, msec2; // the start time format is: hours:minutes:seconds,milliseconds // 00:00:24,600 --> 00:00:27,800 if (sscanf(data.c_str(), "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d", &hour1, &min1, &sec1, &msec1, &hour2, &min2, &sec2, &msec2) != 8) { return ERROR_MALFORMED; } *startTimeUs = ((hour1 * 3600 + min1 * 60 + sec1) * 1000 + msec1) * 1000ll; info->endTimeUs = ((hour2 * 3600 + min2 * 60 + sec2) * 1000 + msec2) * 1000ll; if (info->endTimeUs <= *startTimeUs) { return ERROR_MALFORMED; } info->offset = *offset; bool needMoreData = true; while (needMoreData) { if ((err = readNextLine(offset, &data)) != OK) { if (err == ERROR_END_OF_STREAM) { needMoreData = false; } else { return err; } } if (needMoreData) { data.trim(); if (data.empty()) { // it's an empty line used to separate two subtitles needMoreData = false; } } } info->textLen = *offset - info->offset; return OK; } status_t TimedTextParser::getText( AString *text, int64_t *startTimeUs, int64_t *endTimeUs, const MediaSource::ReadOptions *options) { Mutex::Autolock autoLock(mLock); text->clear(); int64_t seekTimeUs; MediaSource::ReadOptions::SeekMode mode; if (options && options->getSeekTo(&seekTimeUs, &mode)) { int64_t lastEndTimeUs = mTextVector.valueAt(mTextVector.size() - 1).endTimeUs; int64_t firstStartTimeUs = mTextVector.keyAt(0); if (seekTimeUs < 0 || seekTimeUs > lastEndTimeUs) { return ERROR_OUT_OF_RANGE; } else if (seekTimeUs < firstStartTimeUs) { mIndex = 0; } else { // binary search ssize_t low = 0; ssize_t high = mTextVector.size() - 1; ssize_t mid = 0; int64_t currTimeUs; while (low <= high) { mid = low + (high - low)/2; currTimeUs = mTextVector.keyAt(mid); const int diff = currTimeUs - seekTimeUs; if (diff == 0) { break; } else if (diff < 0) { low = mid + 1; } else { if ((high == mid + 1) && (seekTimeUs < mTextVector.keyAt(high))) { break; } high = mid - 1; } } mIndex = mid; } } TextInfo info = mTextVector.valueAt(mIndex); *startTimeUs = mTextVector.keyAt(mIndex); *endTimeUs = info.endTimeUs; mIndex ++; char *str = new char[info.textLen]; if (mDataSource->readAt(info.offset, str, info.textLen) < info.textLen) { delete[] str; return ERROR_IO; } text->append(str, info.textLen); delete[] str; return OK; } } // namespace android