diff --git a/lib/subtitles.cpp b/lib/subtitles.cpp new file mode 100644 index 00000000..defe0ec2 --- /dev/null +++ b/lib/subtitles.cpp @@ -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; + } + + + +} diff --git a/lib/subtitles.h b/lib/subtitles.h new file mode 100644 index 00000000..5ca60d57 --- /dev/null +++ b/lib/subtitles.h @@ -0,0 +1,15 @@ +#pragma once +#include +#include "dtsc.h" + +namespace Subtitle { + + struct Packet { + std::string subtitle; + uint64_t duration; + }; + + Packet getSubtitle(DTSC::Packet packet, DTSC::Meta meta); + +} + diff --git a/src/input/input.cpp b/src/input/input.cpp index 49b1740c..a16c88a1 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -131,8 +131,8 @@ namespace Mist { srtTrack = myMeta.tracks.rbegin()->first + 1; myMeta.tracks[srtTrack].trackID = srtTrack; - myMeta.tracks[srtTrack].type = "subtitle"; - myMeta.tracks[srtTrack].codec = "srt"; + myMeta.tracks[srtTrack].type = "meta"; + myMeta.tracks[srtTrack].codec = "subtitle"; getNextSrt(); while (srtPack){ diff --git a/src/input/input_mp4.cpp b/src/input/input_mp4.cpp index 1496c1a6..3399372f 100644 --- a/src/input/input_mp4.cpp +++ b/src/input/input_mp4.cpp @@ -14,7 +14,7 @@ #include "input_mp4.h" -namespace Mist { +namespace Mist{ mp4TrackHeader::mp4TrackHeader(){ initialised = false; @@ -136,7 +136,7 @@ namespace Mist { 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; data = (char*)malloc(malSize); capa["name"] = "MP4"; @@ -156,18 +156,18 @@ namespace Mist { free(data); } - bool inputMP4::checkArguments() { - if (config->getString("input") == "-") { + bool inputMP4::checkArguments(){ + if (config->getString("input") == "-"){ std::cerr << "Input from stdin not yet supported" << std::endl; return false; } if (!config->getString("streamname").size()){ - if (config->getString("output") == "-") { + if (config->getString("output") == "-"){ std::cerr << "Output to stdout not yet supported" << std::endl; return false; } }else{ - if (config->getString("output") != "-") { + if (config->getString("output") != "-"){ std::cerr << "File output in player mode not supported" << std::endl; return false; } @@ -176,18 +176,18 @@ namespace Mist { return true; } - bool inputMP4::preRun() { + bool inputMP4::preRun(){ //open File inFile = fopen(config->getString("input").c_str(), "r"); - if (!inFile) { + if (!inFile){ return false; } return true; } - bool inputMP4::readHeader() { - if (!inFile) { + bool inputMP4::readHeader(){ + if (!inFile){ INFO_MSG("inFile failed!"); return false; } @@ -306,7 +306,7 @@ namespace Mist { initBox = vEntryBox.getPASP(); if (initBox.isType("hvcC")){ myMeta.tracks[trackNo].init.assign(initBox.payload(), initBox.payloadSize()); - } + } } if (sType == "mp4a" || sType == "aac " || sType == "ac-3"){ MP4::AudioSampleEntry & aEntryBox = (MP4::AudioSampleEntry&)sEntryBox; @@ -325,8 +325,8 @@ namespace Mist { } if (sType == "tx3g"){//plain text subtitles - myMeta.tracks[trackNo].type = "subtitle"; - myMeta.tracks[trackNo].codec = "TTXT"; + myMeta.tracks[trackNo].type = "meta"; + myMeta.tracks[trackNo].codec = "subtitle"; } MP4::STSS stssBox = stblBox.getChild(); @@ -407,7 +407,21 @@ namespace Mist { }else{ BsetPart.timeOffset = 0; } - myMeta.update(BsetPart.time, BsetPart.timeOffset, trackNo, stszBox.getEntrySize(stszIndex), BsetPart.bpos, BsetPart.keyframe); + + 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); + } } } continue; @@ -424,7 +438,7 @@ namespace Mist { 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()){ thisPacket.null(); return; @@ -459,13 +473,31 @@ namespace Mist { return; } - - if (myMeta.tracks[curPart.trackID].codec == "TTXT"){ + if (myMeta.tracks[curPart.trackID].codec == "subtitle"){ unsigned int txtLen = Bit::btohs(data); - if (!txtLen){ - thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, " ", 1, 0/*Note: no bpos*/, isKeyframe); + if (!txtLen && false ){ + curPart.index ++; + return getNext(smart); + //thisPacket.genericFill(curPart.time, curPart.offset, curPart.trackID, " ", 1, 0/*Note: no bpos*/, isKeyframe); }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{ 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(); //for all tracks curPositions.clear(); @@ -509,16 +541,16 @@ namespace Mist { }//rof all tracks } - void inputMP4::trackSelect(std::string trackSpec) { + void inputMP4::trackSelect(std::string trackSpec){ selectedTracks.clear(); long long int index; - while (trackSpec != "") { + while (trackSpec != ""){ index = trackSpec.find(' '); 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); - if (index != std::string::npos) { + if (index != std::string::npos){ trackSpec.erase(0, index + 1); - } else { + }else{ trackSpec = ""; } } diff --git a/src/output/output_hls.cpp b/src/output/output_hls.cpp index 4409f248..c5bd86e5 100644 --- a/src/output/output_hls.cpp +++ b/src/output/output_hls.cpp @@ -35,12 +35,19 @@ namespace Mist { if (audioId != -1) { 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; if (audioId != -1) { result << "_" << audioId; } 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) { @@ -145,10 +152,15 @@ namespace Mist { duration = myMeta.tracks[tid].lastms - starttime; } char lineBuf[400]; - 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()); + + 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{ - snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts\r\n", (double)duration/1000, starttime, starttime + duration); + 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()); + }else{ + snprintf(lineBuf, 400, "#EXTINF:%f,\r\n%lld_%lld.ts\r\n", (double)duration/1000, starttime, starttime + duration); + } } durs.push_back(duration); total_dur += duration; diff --git a/src/output/output_srt.cpp b/src/output/output_srt.cpp index 89e7eb67..f7eaa356 100644 --- a/src/output/output_srt.cpp +++ b/src/output/output_srt.cpp @@ -14,8 +14,7 @@ namespace Mist { capa["desc"] = "Enables HTTP protocol subtitle streaming in subrip and WebVTT formats."; capa["url_match"].append("/$.srt"); capa["url_match"].append("/$.vtt"); - capa["codecs"][0u][0u].append("srt"); - capa["codecs"][0u][0u].append("TTXT"); + capa["codecs"][0u][0u].append("subtitle"); capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/text/plain"; capa["methods"][0u]["priority"] = 8ll; @@ -30,6 +29,7 @@ namespace Mist { char * dataPointer = 0; unsigned int len = 0; thisPacket.getString("data", dataPointer, len); +// INFO_MSG("getting sub: %s", dataPointer); //ignore empty subs if (len == 0 || (len == 1 && dataPointer[0] == ' ')){ return; @@ -39,6 +39,20 @@ namespace Mist { tmp << lastNum++ << std::endl; } 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]; int tmpLen = sprintf(tmpBuf, "%.2llu:%.2llu:%.2llu.%.3llu", (time / 3600000), ((time % 3600000) / 60000), (((time % 3600000) % 60000) / 1000), time % 1000); tmp.write(tmpBuf, tmpLen); @@ -79,6 +93,18 @@ namespace Mist { selectedTracks.clear(); 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.setCORSHeaders(); if(method == "OPTIONS" || method == "HEAD"){ diff --git a/src/output/output_srt.h b/src/output/output_srt.h index 68ef82c2..93d81f15 100644 --- a/src/output/output_srt.h +++ b/src/output/output_srt.h @@ -13,6 +13,9 @@ namespace Mist { protected: bool webVTT; int lastNum; + uint32_t filter_from; + uint32_t filter_to; + uint32_t index; }; }