Skip to content

Commit

Permalink
add support for dfxp captions
Browse files Browse the repository at this point in the history
  • Loading branch information
erankor committed Mar 31, 2017
1 parent 4cf6f23 commit 99c7434
Show file tree
Hide file tree
Showing 14 changed files with 1,086 additions and 19 deletions.
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -27,7 +27,8 @@
Input:
1. WebVTT
2. SRT
3. CAP (Cheetah)
3. DFXP/TTML
4. CAP (Cheetah)

Output:
1. DASH - served as a single WebVTT
Expand Down
19 changes: 19 additions & 0 deletions config
Expand Up @@ -83,6 +83,23 @@ if [ $ngx_found = yes ]; then
CORE_LIBS="$CORE_LIBS $LIB_AV_FILTER $LIB_AV_UTIL"
fi

# libxml2
#
ngx_feature="libxml2"
ngx_feature_name="NGX_HAVE_LIBXML2"
ngx_feature_run=no
ngx_feature_incs="#include <libxml/parser.h>
#include <libxml/tree.h>"
ngx_feature_path="/usr/include/libxml2"
ngx_feature_libs="-lxml2"
ngx_feature_test="xmlReadMemory(NULL, 0, NULL, NULL, 0);"
. auto/feature

if [ $ngx_found = yes ]; then
CORE_INCS="$CORE_INCS $ngx_feature_path"
CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
fi

VOD_DEPS="$ngx_addon_dir/ngx_async_open_file_cache.h \
$ngx_addon_dir/ngx_buffer_cache.h \
$ngx_addon_dir/ngx_buffer_cache_internal.h \
Expand Down Expand Up @@ -176,6 +193,7 @@ VOD_DEPS="$ngx_addon_dir/ngx_async_open_file_cache.h \
$ngx_addon_dir/vod/mss/mss_playready.h \
$ngx_addon_dir/vod/thumb/thumb_grabber.h \
$ngx_addon_dir/vod/subtitle/cap_format.h \
$ngx_addon_dir/vod/subtitle/dfxp_format.h \
$ngx_addon_dir/vod/subtitle/subtitle_format.h \
$ngx_addon_dir/vod/subtitle/ttml_builder.h \
$ngx_addon_dir/vod/subtitle/webvtt_builder.h \
Expand Down Expand Up @@ -257,6 +275,7 @@ VOD_SRCS="$ngx_addon_dir/ngx_async_open_file_cache.c \
$ngx_addon_dir/vod/mss/mss_playready.c \
$ngx_addon_dir/vod/thumb/thumb_grabber.c \
$ngx_addon_dir/vod/subtitle/cap_format.c \
$ngx_addon_dir/vod/subtitle/dfxp_format.c \
$ngx_addon_dir/vod/subtitle/subtitle_format.c \
$ngx_addon_dir/vod/subtitle/ttml_builder.c \
$ngx_addon_dir/vod/subtitle/webvtt_builder.c \
Expand Down
2 changes: 1 addition & 1 deletion ngx_http_vod_conf.c
Expand Up @@ -1266,7 +1266,7 @@ ngx_command_t ngx_http_vod_commands[] = {
};

ngx_http_module_t ngx_http_vod_module_ctx = {
ngx_http_vod_add_variables, /* preconfiguration */
ngx_http_vod_preconfiguration, /* preconfiguration */
ngx_http_vod_init_parsers, /* postconfiguration */

NULL, /* create main configuration */
Expand Down
12 changes: 9 additions & 3 deletions ngx_http_vod_module.c
Expand Up @@ -17,6 +17,7 @@
#include "vod/mp4/mp4_format.h"
#include "vod/mkv/mkv_format.h"
#include "vod/subtitle/webvtt_format.h"
#include "vod/subtitle/dfxp_format.h"
#include "vod/subtitle/cap_format.h"
#include "vod/input/read_cache.h"
#include "vod/filters/audio_filter.h"
Expand Down Expand Up @@ -223,8 +224,8 @@ ngx_module_t ngx_http_vod_module = {
ngx_http_vod_process_init, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
dfxp_exit_process, /* exit process */
dfxp_exit_process, /* exit master */
NGX_MODULE_V1_PADDING
};

Expand All @@ -235,6 +236,9 @@ static media_format_t* media_formats[] = {
&mp4_format,
// XXXXX add &mkv_format,
&webvtt_format,
#if (VOD_HAVE_LIBXML2)
&dfxp_format,
#endif
&cap_format,
NULL
};
Expand Down Expand Up @@ -578,7 +582,7 @@ static ngx_http_vod_variable_t ngx_http_vod_variables[] = {
};

ngx_int_t
ngx_http_vod_add_variables(ngx_conf_t *cf)
ngx_http_vod_preconfiguration(ngx_conf_t *cf)
{
ngx_http_vod_variable_t* vars_cur = ngx_http_vod_variables;
ngx_http_vod_variable_t* vars_end = vars_cur + vod_array_entries(ngx_http_vod_variables);
Expand All @@ -604,6 +608,8 @@ ngx_http_vod_add_variables(ngx_conf_t *cf)

ngx_http_vod_set_status_index(rc);

dfxp_init_process();

return NGX_OK;
}

Expand Down
2 changes: 1 addition & 1 deletion ngx_http_vod_module.h
Expand Up @@ -12,7 +12,7 @@ extern ngx_module_t ngx_http_vod_module;
ngx_int_t ngx_http_vod_handler(ngx_http_request_t *r);

// variables
ngx_int_t ngx_http_vod_add_variables(ngx_conf_t *cf);
ngx_int_t ngx_http_vod_preconfiguration(ngx_conf_t *cf);

// handlers
ngx_int_t ngx_http_vod_local_request_handler(ngx_http_request_t *r);
Expand Down
81 changes: 71 additions & 10 deletions test/validate_timestamps.py
Expand Up @@ -18,7 +18,12 @@ def getTimingInfoFromTrunAtom(trunAtom, startPts):
trunFlags = struct.unpack('>L', trunAtom[:4])[0]
durations = []
ptsDelays = []
if trunFlags == 0xf01:

if trunFlags == 0x01000f01:
for pos in xrange(12, len(trunAtom), 16):
durations.append(struct.unpack('>L', trunAtom[pos:(pos + 4)])[0])
ptsDelays.append(struct.unpack('>l', trunAtom[(pos + 12):(pos + 16)])[0])
elif trunFlags == 0xf01:
for pos in xrange(12, len(trunAtom), 16):
durations.append(struct.unpack('>L', trunAtom[pos:(pos + 4)])[0])
ptsDelays.append(struct.unpack('>L', trunAtom[(pos + 12):(pos + 16)])[0])
Expand Down Expand Up @@ -72,7 +77,9 @@ def parse24be(buf):
def getFragmentInfoFromDtssPtss(url, segIndex, fileIndex, dtss, ptss, audioPacketsCount = None):
result = []
for streamId in dtss:
if audioPacketsCount == None or streamId != 'a1':
if len(dtss[streamId]) == 1:
lastDuration = 0
elif audioPacketsCount == None or streamId != 'a1':
lastDuration = (dtss[streamId][-1] - dtss[streamId][0]) / (len(dtss[streamId]) - 1)
else:
avgDuration = (dtss[streamId][-1] - dtss[streamId][0]) / sum(audioPacketsCount[:-1])
Expand Down Expand Up @@ -140,9 +147,11 @@ def countAdtsPackets(d):
theAdtsHeader = adtsHeader.parse(d[curPos:])
result += 1
curPos += theAdtsHeader.aac_frame_length
if theAdtsHeader.aac_frame_length == 0:
break
return result

def getHlsFragmentInfo(url):
def getHlsFragmentInfo(url, fileIndex):
d = urllib2.urlopen(url).read()
dtss = {}
ptss = {}
Expand Down Expand Up @@ -207,16 +216,27 @@ def getHlsFragmentInfo(url):
adtsCounts.append(countAdtsPackets(audioPacket))
urlFilename = url.rsplit('/', 1)[-1]
urlFilename = urlFilename.replace('-v1', '').replace('-a1', '').replace('-Seg1-Frag', '-').replace('.ts', '')
_, segIndex, fileIndex = urlFilename.split('-')
if fileIndex > 0:
segIndex = urlFilename.split('-')[1]
else:
_, segIndex, fileIndex = urlFilename.split('-')
return getFragmentInfoFromDtssPtss(url, segIndex, fileIndex, dtss, ptss, adtsCounts)

def getHlsFragmentsInfo(urls):
result = []
baseUrls = []
for url in urls:
baseUrl = url.rsplit('/', 1)[0]
if url.endswith('.m3u8') and not baseUrl.endswith('.urlset/'):
baseUrls.append(baseUrl)
if not url.endswith('.ts'):
continue
print '.',
result += getHlsFragmentInfo(url)
if baseUrl in baseUrls:
fileIndex = baseUrls.index(baseUrl) + 1
else:
fileIndex = 0
result += getHlsFragmentInfo(url, fileIndex)
return result

def getMssFragmentInfo(url):
Expand Down Expand Up @@ -254,7 +274,7 @@ def getMssFragmentsInfo(urls):
print '.',
result += getMssFragmentInfo(url)
return result

res = urllib2.urlopen(URL)
mimeType = res.info().getheader('Content-Type')
d = res.read()
Expand All @@ -266,11 +286,21 @@ def getMssFragmentsInfo(urls):
'text/xml': getMssFragmentsInfo,
}

TIMESCALE = {
'application/dash+xml': 90000,
'video/f4m': 1000,
'application/vnd.apple.mpegurl': 90000,
'text/xml': 10000000,
}

baseUrl = URL.rsplit('/', 1)[0] + '/'
urls = manifest_utils.getManifestUrls(baseUrl, d, mimeType, {})

print 'processing %s urls' % len(urls)
fragmentInfos = PARSER_BY_MIME_TYPE[mimeType](urls)
timescale = TIMESCALE[mimeType]

print ''

# group the results
byStream = {}
Expand All @@ -284,6 +314,8 @@ def getMssFragmentsInfo(urls):
bySegIndex.setdefault(key, {})
bySegIndex[key][fileIndex] = (url, timingInfo)

errors = []

# consistency within each stream
for streamId in byStream:
segIndexes = sorted(byStream[streamId].keys())
Expand All @@ -296,14 +328,26 @@ def getMssFragmentsInfo(urls):
if expectedDts != None:
gapSize = abs(timingInfo[START_DTS] - expectedDts)
if gapSize > THRESHOLD:
print 'in-stream dts gap, size=%s, expected=%s, actual=%s, stream=%s, url=%s' % (gapSize, expectedDts, timingInfo[START_DTS], fullStreamId, url)
errors.append(['in-stream dts gap',
timingInfo[START_DTS],
'size=%s (%.3f)' % (gapSize, gapSize / float(timescale)),
'expected=%s (%.3f)' % (expectedDts, expectedDts / float(timescale)),
'actual=%s (%.3f)' % (timingInfo[START_DTS], timingInfo[START_DTS] / float(timescale)),
'stream=%s' % streamId,
'url=%s' % url])
expectedDts = timingInfo[END_DTS]

# pts
if expectedPts != None:
gapSize = abs(timingInfo[START_PTS] - expectedPts)
if gapSize > THRESHOLD:
print 'in-stream pts gap, size=%s, expected=%s, actual=%s, stream=%s, url=%s' % (gapSize, expectedPts, timingInfo[START_PTS], fullStreamId, url)
errors.append(['in-stream pts gap',
timingInfo[START_PTS],
'size=%s (%.3f)' % (gapSize, gapSize / float(timescale)),
'expected=%s (%.3f)' % (expectedPts, expectedPts / float(timescale)),
'actual=%s (%.3f)' % (timingInfo[START_PTS], timingInfo[START_PTS] / float(timescale)),
'stream=%s' % streamId,
'url=%s' % url])
expectedPts = timingInfo[END_PTS]

# consistency cross stream
Expand All @@ -330,11 +374,28 @@ def getMssFragmentsInfo(urls):
# dts
gapSize = maxDtsInfo[1][START_DTS] - minDtsInfo[1][START_DTS]
if gapSize > THRESHOLD:
print 'cross-stream dts gap, size=%s, min=%s, max=%s, minurl=%s maxurl=%s' % (gapSize, minDtsInfo[1][START_DTS], maxDtsInfo[1][START_DTS], minDtsInfo[0], maxDtsInfo[0])
errors.append(['cross-stream dts gap',
minDtsInfo[1][START_DTS],
'size=%s (%.3f)' % (gapSize, gapSize / float(timescale)),
'min=%s (%.3f)' % (minDtsInfo[1][START_DTS], minDtsInfo[1][START_DTS] / float(timescale)),
'max=%s (%.3f)' % (maxDtsInfo[1][START_DTS], maxDtsInfo[1][START_DTS] / float(timescale)),
'stream=%s' % segIndex,
'minurl=%s' % minDtsInfo[0],
'maxurl=%s' % maxDtsInfo[0]])

# pts
gapSize = maxPtsInfo[1][START_PTS] - minPtsInfo[1][START_PTS]
if gapSize > THRESHOLD:
print 'cross-stream pts gap, size=%s, min=%s, max=%s, minurl=%s maxurl=%s' % (gapSize, minPtsInfo[1][START_PTS], maxPtsInfo[1][START_PTS], minPtsInfo[0], maxPtsInfo[0])
errors.append(['cross-stream pts gap',
minPtsInfo[1][START_PTS],
'size=%s (%.3f)' % (gapSize, gapSize / float(timescale)),
'min=%s (%.3f)' % (minPtsInfo[1][START_PTS], minPtsInfo[1][START_PTS] / float(timescale)),
'max=%s (%.3f)' % (maxPtsInfo[1][START_PTS], maxPtsInfo[1][START_PTS] / float(timescale)),
'stream=%s' % segIndex,
'minurl=%s' % minPtsInfo[0],
'maxurl=%s' % maxPtsInfo[0]])

for cells in sorted(errors):
print cells[0] + ', ' + '\n\t'.join(cells[2:]) + '\n'

print 'done'
11 changes: 9 additions & 2 deletions vod/common.h
Expand Up @@ -96,6 +96,7 @@ void vod_log_error(vod_uint_t level, vod_log_t *log, int err,
#define VOD_HAVE_LIB_AV_CODEC NGX_HAVE_LIB_AV_CODEC
#define VOD_HAVE_LIB_AV_FILTER NGX_HAVE_LIB_AV_FILTER
#define VOD_HAVE_OPENSSL_EVP NGX_HAVE_OPENSSL_EVP
#define VOD_HAVE_LIBXML2 NGX_HAVE_LIBXML2

#if (VOD_HAVE_LIB_AV_CODEC)
#include <libavcodec/avcodec.h>
Expand All @@ -121,6 +122,7 @@ void vod_log_error(vod_uint_t level, vod_log_t *log, int err,
#define VOD_AGAIN NGX_AGAIN

#define vod_inline ngx_inline
#define vod_cdecl ngx_cdecl

// memory set/copy functions
#define vod_memcpy(dst, src, n) ngx_memcpy(dst, src, n)
Expand All @@ -139,10 +141,13 @@ void vod_log_error(vod_uint_t level, vod_log_t *log, int err,
// string functions
#define vod_sprintf ngx_sprintf
#define vod_snprintf ngx_snprintf
#define vod_strncmp(s1, s2, n) ngx_strncmp(s1, s2, n)
#define vod_strncasecmp(s1, s2, n) ngx_strncasecmp(s1, s2, n)
#define vod_atoi(str, len) ngx_atoi(str, len)
#define vod_atofp(str, len, point) ngx_atofp(str, len, point)
#define vod_strstrn ngx_strstrn
#define vod_strcmp ngx_strcmp
#define vod_strlen ngx_strlen
#define vod_strncmp(s1, s2, n) ngx_strncmp(s1, s2, n)
#define vod_strncasecmp(s1, s2, n) ngx_strncasecmp(s1, s2, n)
#define vod_pstrdup(pool, src) ngx_pstrdup(pool, src)

// array functions
Expand Down Expand Up @@ -212,6 +217,8 @@ void vod_log_error(vod_uint_t level, vod_log_t *log, int err,
#define vod_base64_decoded_length(len) ngx_base64_decoded_length(len)
#define vod_crc32_short(p, len) ngx_crc32_short(p, len)

#define VOD_MAX_ERROR_STR NGX_MAX_ERROR_STR

#define VOD_LOG_STDERR NGX_LOG_STDERR
#define VOD_LOG_EMERG NGX_LOG_EMERG
#define VOD_LOG_ALERT NGX_LOG_ALERT
Expand Down
1 change: 1 addition & 0 deletions vod/media_format.h
Expand Up @@ -93,6 +93,7 @@ enum {
FORMAT_ID_MKV,
FORMAT_ID_WEBVTT,
FORMAT_ID_CAP,
FORMAT_ID_DFXP,
};

enum { // mp4 only
Expand Down
1 change: 1 addition & 0 deletions vod/subtitle/cap_format.c
Expand Up @@ -146,6 +146,7 @@ cap_parse(
request_context,
parse_params,
source,
NULL,
cap_get_duration(source),
metadata_part_count,
result);
Expand Down

0 comments on commit 99c7434

Please sign in to comment.