Reworked existing subtitle support (sideloaded, MP4 in and srt out)

This commit is contained in:
Ramoe 2017-11-23 09:13:43 +01:00 committed by Thulinma
parent 741c4755cc
commit b9e261e1ef
7 changed files with 159 additions and 33 deletions

38
lib/subtitles.cpp Normal file
View file

@ -0,0 +1,38 @@
#include "subtitles.h"
#include "bitfields.h"
#include "defines.h"
namespace Subtitle {
Packet getSubtitle(DTSC::Packet packet, DTSC::Meta meta) {
char * tmp = 0;
uint16_t length = 0;
unsigned int len;
Packet output;
long int trackId= packet.getTrackId();
if(meta.tracks[trackId].codec != "TTXT" && meta.tracks[trackId].codec != "SRT") {
//no subtitle track
return output;
}
if(packet.hasMember("duration")) {
output.duration = packet.getInt("duration");
} else {
//get parts from meta
//calculate duration
}
packet.getString("data", output.subtitle);
if(meta.tracks[trackId].codec == "TTXT") {
unsigned short size = Bit::btohs(output.subtitle.c_str());
output.subtitle = output.subtitle.substr(2,size);
}
return output;
}
}

15
lib/subtitles.h Normal file
View file

@ -0,0 +1,15 @@
#pragma once
#include <string>
#include "dtsc.h"
namespace Subtitle {
struct Packet {
std::string subtitle;
uint64_t duration;
};
Packet getSubtitle(DTSC::Packet packet, DTSC::Meta meta);
}

View file

@ -131,8 +131,8 @@ namespace Mist {
srtTrack = myMeta.tracks.rbegin()->first + 1; srtTrack = myMeta.tracks.rbegin()->first + 1;
myMeta.tracks[srtTrack].trackID = srtTrack; myMeta.tracks[srtTrack].trackID = srtTrack;
myMeta.tracks[srtTrack].type = "subtitle"; myMeta.tracks[srtTrack].type = "meta";
myMeta.tracks[srtTrack].codec = "srt"; myMeta.tracks[srtTrack].codec = "subtitle";
getNextSrt(); getNextSrt();
while (srtPack){ while (srtPack){

View file

@ -14,7 +14,7 @@
#include "input_mp4.h" #include "input_mp4.h"
namespace Mist { namespace Mist{
mp4TrackHeader::mp4TrackHeader(){ mp4TrackHeader::mp4TrackHeader(){
initialised = false; initialised = false;
@ -136,7 +136,7 @@ namespace Mist {
size = stszBox.getEntrySize(index); size = stszBox.getEntrySize(index);
} }
inputMP4::inputMP4(Util::Config * cfg) : Input(cfg) { inputMP4::inputMP4(Util::Config * cfg) : Input(cfg){
malSize = 4;//initialise data read buffer to 0; malSize = 4;//initialise data read buffer to 0;
data = (char*)malloc(malSize); data = (char*)malloc(malSize);
capa["name"] = "MP4"; capa["name"] = "MP4";
@ -156,18 +156,18 @@ namespace Mist {
free(data); free(data);
} }
bool inputMP4::checkArguments() { bool inputMP4::checkArguments(){
if (config->getString("input") == "-") { if (config->getString("input") == "-"){
std::cerr << "Input from stdin not yet supported" << std::endl; std::cerr << "Input from stdin not yet supported" << std::endl;
return false; return false;
} }
if (!config->getString("streamname").size()){ if (!config->getString("streamname").size()){
if (config->getString("output") == "-") { if (config->getString("output") == "-"){
std::cerr << "Output to stdout not yet supported" << std::endl; std::cerr << "Output to stdout not yet supported" << std::endl;
return false; return false;
} }
}else{ }else{
if (config->getString("output") != "-") { if (config->getString("output") != "-"){
std::cerr << "File output in player mode not supported" << std::endl; std::cerr << "File output in player mode not supported" << std::endl;
return false; return false;
} }
@ -176,18 +176,18 @@ namespace Mist {
return true; return true;
} }
bool inputMP4::preRun() { bool inputMP4::preRun(){
//open File //open File
inFile = fopen(config->getString("input").c_str(), "r"); inFile = fopen(config->getString("input").c_str(), "r");
if (!inFile) { if (!inFile){
return false; return false;
} }
return true; return true;
} }
bool inputMP4::readHeader() { bool inputMP4::readHeader(){
if (!inFile) { if (!inFile){
INFO_MSG("inFile failed!"); INFO_MSG("inFile failed!");
return false; return false;
} }
@ -325,8 +325,8 @@ namespace Mist {
} }
if (sType == "tx3g"){//plain text subtitles if (sType == "tx3g"){//plain text subtitles
myMeta.tracks[trackNo].type = "subtitle"; myMeta.tracks[trackNo].type = "meta";
myMeta.tracks[trackNo].codec = "TTXT"; myMeta.tracks[trackNo].codec = "subtitle";
} }
MP4::STSS stssBox = stblBox.getChild<MP4::STSS>(); MP4::STSS stssBox = stblBox.getChild<MP4::STSS>();
@ -407,9 +407,23 @@ namespace Mist {
}else{ }else{
BsetPart.timeOffset = 0; BsetPart.timeOffset = 0;
} }
if(sType == "tx3g"){
if(stszBox.getEntrySize(stszIndex) <=2 && false){
FAIL_MSG("size <=2");
}else{
long long packSendSize = 0;
packSendSize = 24 + (BsetPart.timeOffset ? 17 : 0) + (BsetPart.bpos ? 15 : 0) + 19 +
stszBox.getEntrySize(stszIndex) + 11-2 + 19;
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo,
stszBox.getEntrySize(stszIndex) -2 , BsetPart.bpos, true,
packSendSize);
}
}else{
myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo, stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe); myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo, stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe);
} }
} }
}
continue; continue;
} }
if (!MP4::skipBox(inFile)){//moving on to next box if (!MP4::skipBox(inFile)){//moving on to next box
@ -424,7 +438,7 @@ namespace Mist {
return true; return true;
} }
void inputMP4::getNext(bool smart) {//get next part from track in stream void inputMP4::getNext(bool smart){//get next part from track in stream
if (curPositions.empty()){ if (curPositions.empty()){
thisPacket.null(); thisPacket.null();
return; return;
@ -459,13 +473,31 @@ namespace Mist {
return; return;
} }
if (myMeta.tracks[curPart.trackID].codec == "subtitle"){
if (myMeta.tracks[curPart.trackID].codec == "TTXT"){
unsigned int txtLen = Bit::btohs(data); unsigned int txtLen = Bit::btohs(data);
if (!txtLen){ if (!txtLen && false ){
thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, " ", 1, 0/*Note: no bpos*/, isKeyframe); curPart.index ++;
return getNext(smart);
//thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, " ", 1, 0/*Note: no bpos*/, isKeyframe);
}else{ }else{
thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data+2, txtLen, 0/*Note: no bpos*/, isKeyframe);
static JSON::Value thisPack;
thisPack.null();
thisPack["trackid"] = (long long)curPart.trackID;
thisPack["bpos"] = (long long)curPart.bpos; //(long long)fileSource.tellg();
thisPack["data"] = std::string(data+2,txtLen);
// thisPack["index"] = index;
thisPack["time"] = (long long)curPart.time;
thisPack["duration"] = 1000;
// thisPack["time"] = (long long)timestamp;
thisPack["keyframe"] = true;
// Write the json value to lastpack
std::string tmpStr = thisPack.toNetPacked();
thisPacket.reInit(tmpStr.data(), tmpStr.size());
//return;
//thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data+2, txtLen, 0/*Note: no bpos*/, isKeyframe);
} }
}else{ }else{
thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data, curPart.size, 0/*Note: no bpos*/, isKeyframe); thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, data, curPart.size, 0/*Note: no bpos*/, isKeyframe);
@ -479,7 +511,7 @@ namespace Mist {
} }
} }
void inputMP4::seek(int seekTime) {//seek to a point void inputMP4::seek(int seekTime){//seek to a point
nextKeyframe.clear(); nextKeyframe.clear();
//for all tracks //for all tracks
curPositions.clear(); curPositions.clear();
@ -509,16 +541,16 @@ namespace Mist {
}//rof all tracks }//rof all tracks
} }
void inputMP4::trackSelect(std::string trackSpec) { void inputMP4::trackSelect(std::string trackSpec){
selectedTracks.clear(); selectedTracks.clear();
long long int index; long long int index;
while (trackSpec != "") { while (trackSpec != ""){
index = trackSpec.find(' '); index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str())); selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
VERYHIGH_MSG("Added track %d, index = %lld, (index == npos) = %d", atoi(trackSpec.substr(0, index).c_str()), index, index == std::string::npos); VERYHIGH_MSG("Added track %d, index = %lld, (index == npos) = %d", atoi(trackSpec.substr(0, index).c_str()), index, index == std::string::npos);
if (index != std::string::npos) { if (index != std::string::npos){
trackSpec.erase(0, index + 1); trackSpec.erase(0, index + 1);
} else { }else{
trackSpec = ""; trackSpec = "";
} }
} }

View file

@ -35,12 +35,19 @@ namespace Mist {
if (audioId != -1) { if (audioId != -1) {
bWidth += myMeta.tracks[audioId].bps; bWidth += myMeta.tracks[audioId].bps;
} }
result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8) << "\r\n"; result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,SUBTITLES=\"sub1\",BANDWIDTH=" << (bWidth * 8) << "\r\n";
result << it->first; result << it->first;
if (audioId != -1) { if (audioId != -1) {
result << "_" << audioId; result << "_" << audioId;
} }
result << "/index.m3u8?sessId=" << getpid() << "\r\n"; result << "/index.m3u8?sessId=" << getpid() << "\r\n";
}else if(it->second.codec == "subtitle"){
if(it->second.lang.empty()){
it->second.lang = "und";
}
result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << it->second.lang << "\",NAME=\"" << Encodings::ISO639::decode(it->second.lang) << "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << it->first << "/index.m3u8\"" << "\r\n";
} }
} }
if (!vidTracks && audioId) { if (!vidTracks && audioId) {
@ -145,11 +152,16 @@ namespace Mist {
duration = myMeta.tracks[tid].lastms - starttime; duration = myMeta.tracks[tid].lastms - starttime;
} }
char lineBuf[400]; char lineBuf[400];
if(myMeta.tracks[tid].codec == "subtitle"){
snprintf(lineBuf, 400, "#EXTINF:%f,\r\n../../../%s.vtt?track=%d&from=%lld&to=%lld\r\n", streamName.c_str(),(double)duration/1000,tid, starttime, starttime + duration);
}else{
if (sessId.size()){ if (sessId.size()){
snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts?sessId=%s\r\n", (double)duration/1000, starttime, starttime + duration, sessId.c_str()); snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts?sessId=%s\r\n", (double)duration/1000, starttime, starttime + duration, sessId.c_str());
}else{ }else{
snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts\r\n", (double)duration/1000, starttime, starttime + duration); snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts\r\n", (double)duration/1000, starttime, starttime + duration);
} }
}
durs.push_back(duration); durs.push_back(duration);
total_dur += duration; total_dur += duration;
lines.push_back(lineBuf); lines.push_back(lineBuf);

View file

@ -14,8 +14,7 @@ namespace Mist {
capa["desc"] = "Enables HTTP protocol subtitle streaming in subrip and WebVTT formats."; capa["desc"] = "Enables HTTP protocol subtitle streaming in subrip and WebVTT formats.";
capa["url_match"].append("/$.srt"); capa["url_match"].append("/$.srt");
capa["url_match"].append("/$.vtt"); capa["url_match"].append("/$.vtt");
capa["codecs"][0u][0u].append("srt"); capa["codecs"][0u][0u].append("subtitle");
capa["codecs"][0u][0u].append("TTXT");
capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "html5/text/plain"; capa["methods"][0u]["type"] = "html5/text/plain";
capa["methods"][0u]["priority"] = 8ll; capa["methods"][0u]["priority"] = 8ll;
@ -30,6 +29,7 @@ namespace Mist {
char * dataPointer = 0; char * dataPointer = 0;
unsigned int len = 0; unsigned int len = 0;
thisPacket.getString("data", dataPointer, len); thisPacket.getString("data", dataPointer, len);
// INFO_MSG("getting sub: %s", dataPointer);
//ignore empty subs //ignore empty subs
if (len == 0 || (len == 1 && dataPointer[0] == ' ')){ if (len == 0 || (len == 1 && dataPointer[0] == ' ')){
return; return;
@ -39,6 +39,20 @@ namespace Mist {
tmp << lastNum++ << std::endl; tmp << lastNum++ << std::endl;
} }
long long unsigned int time = thisPacket.getTime(); long long unsigned int time = thisPacket.getTime();
//filter subtitle in specific timespan
if(filter_from > 0 && time < filter_from){
index++; //when using seek, the index is lost.
seek(filter_from);
return;
}
if(filter_to > 0 && time > filter_to && filter_to > filter_from){
config->is_active = false;
return;
}
char tmpBuf[50]; char tmpBuf[50];
int tmpLen = sprintf(tmpBuf, "%.2llu:%.2llu:%.2llu.%.3llu", (time / 3600000), ((time % 3600000) / 60000), (((time % 3600000) % 60000) / 1000), time % 1000); int tmpLen = sprintf(tmpBuf, "%.2llu:%.2llu:%.2llu.%.3llu", (time / 3600000), ((time % 3600000) / 60000), (((time % 3600000) % 60000) / 1000), time % 1000);
tmp.write(tmpBuf, tmpLen); tmp.write(tmpBuf, tmpLen);
@ -79,6 +93,18 @@ namespace Mist {
selectedTracks.clear(); selectedTracks.clear();
selectedTracks.insert(JSON::Value(H.GetVar("track")).asInt()); selectedTracks.insert(JSON::Value(H.GetVar("track")).asInt());
} }
filter_from = 0;
filter_to = 0;
index = 0;
if (H.GetVar("from") != ""){
filter_from = JSON::Value(H.GetVar("from")).asInt();
}
if (H.GetVar("to") != ""){
filter_to = JSON::Value(H.GetVar("to")).asInt();
}
H.Clean(); H.Clean();
H.setCORSHeaders(); H.setCORSHeaders();
if(method == "OPTIONS" || method == "HEAD"){ if(method == "OPTIONS" || method == "HEAD"){

View file

@ -13,6 +13,9 @@ namespace Mist {
protected: protected:
bool webVTT; bool webVTT;
int lastNum; int lastNum;
uint32_t filter_from;
uint32_t filter_to;
uint32_t index;
}; };
} }