cinera.c: Enable default closed captions
This commit adds support for configurably enabling closed captions by default. Set the "default_cc_lang" in a config file, or "cc_lang" in the [video] node of an .hmml file. Once set, videos which contain captions for this langage will be initialised with these captions enabled. New config setting: default_cc_lang New [video] node field: cc_lang
This commit is contained in:
parent
554f7393ff
commit
6b09247cd2
|
@ -23,7 +23,7 @@ typedef struct
|
||||||
version CINERA_APP_VERSION = {
|
version CINERA_APP_VERSION = {
|
||||||
.Major = 0,
|
.Major = 0,
|
||||||
.Minor = 10,
|
.Minor = 10,
|
||||||
.Patch = 19
|
.Patch = 20
|
||||||
};
|
};
|
||||||
|
|
||||||
#define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
|
#define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
|
||||||
|
@ -1451,6 +1451,7 @@ file credits this person for the entire project, but indexers may \"uncredit\" p
|
||||||
file credits this person for the entire project, but indexers may \"uncredit\" people in the [video] node of a HMML file." },
|
file credits this person for the entire project, but indexers may \"uncredit\" people in the [video] node of a HMML file." },
|
||||||
{ "css_path", "Path relative to assets_root_dir and assets_root_url where CSS files are located." },
|
{ "css_path", "Path relative to assets_root_dir and assets_root_url where CSS files are located." },
|
||||||
{ "db_location", "Absolute file path where the database file resides. If you run multiple instances of Cinera on the same machine, please ensure this db_location differs between them." },
|
{ "db_location", "Absolute file path where the database file resides. If you run multiple instances of Cinera on the same machine, please ensure this db_location differs between them." },
|
||||||
|
{ "default_cc_lang", "The ISO 639-1 language code of the closed captions to enable by default. May be overridden by setting the cc_lang in the video node of an HMML file." },
|
||||||
{ "default_medium", "The ID of a medium (see also medium) which will be the default for the project. May be overridden by setting the medium in the video node of an HMML file." },
|
{ "default_medium", "The ID of a medium (see also medium) which will be the default for the project. May be overridden by setting the medium in the video node of an HMML file." },
|
||||||
{ "deny", "See allow." },
|
{ "deny", "See allow." },
|
||||||
{ "deny_bespoke_templates", "Indexers may use the \"template\" attribute in the video node of an .hmml file to set a bespoke template for that entry, superseding any configured player_template. Setting \"deny_bespoke_templates\" to true prevents this." },
|
{ "deny_bespoke_templates", "Indexers may use the \"template\" attribute in the video node of an .hmml file to set a bespoke template for that entry, superseding any configured player_template. Setting \"deny_bespoke_templates\" to true prevents this." },
|
||||||
|
@ -1586,6 +1587,7 @@ typedef enum
|
||||||
IDENT_CREDIT,
|
IDENT_CREDIT,
|
||||||
IDENT_CSS_PATH,
|
IDENT_CSS_PATH,
|
||||||
IDENT_DB_LOCATION,
|
IDENT_DB_LOCATION,
|
||||||
|
IDENT_DEFAULT_CC_LANG,
|
||||||
IDENT_DEFAULT_MEDIUM,
|
IDENT_DEFAULT_MEDIUM,
|
||||||
IDENT_DENY,
|
IDENT_DENY,
|
||||||
IDENT_DENY_BESPOKE_TEMPLATES,
|
IDENT_DENY_BESPOKE_TEMPLATES,
|
||||||
|
@ -1963,18 +1965,14 @@ ConfigError(string *Filename, uint64_t LineNumber, severity Severity, char *Mess
|
||||||
|
|
||||||
void
|
void
|
||||||
ConfigErrorField(string *Filename, uint64_t LineNumber, severity Severity, config_identifier_id FieldID,
|
ConfigErrorField(string *Filename, uint64_t LineNumber, severity Severity, config_identifier_id FieldID,
|
||||||
char *Message, string *Received)
|
string *Received, char *Message)
|
||||||
{
|
{
|
||||||
ErrorFilenameAndLineNumber(Filename, LineNumber, Severity, ED_CONFIG);
|
ErrorFilenameAndLineNumber(Filename, LineNumber, Severity, ED_CONFIG);
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Faulty %s%s%s %s",
|
"%s%s%s value %s%.*s%s %s\n",
|
||||||
ColourStrings[CS_YELLOW_BOLD], ConfigIdentifiers[FieldID].String, ColourStrings[CS_END],
|
ColourStrings[CS_YELLOW_BOLD], ConfigIdentifiers[FieldID].String, ColourStrings[CS_END],
|
||||||
|
ColourStrings[CS_MAGENTA_BOLD], (int)Received->Length, Received->Base, ColourStrings[CS_END],
|
||||||
Message);
|
Message);
|
||||||
if(Received)
|
|
||||||
{
|
|
||||||
PrintStringC(CS_MAGENTA_BOLD, *Received);
|
|
||||||
}
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -2139,6 +2137,25 @@ IndexingErrorClash(string *Filename, uint64_t LineNumber, severity Severity, cha
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PrintValidLanguageCodeChars(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr,
|
||||||
|
" Valid characters:\n"
|
||||||
|
" a to z\n"
|
||||||
|
" A to Z\n"
|
||||||
|
" 0 to 9\n"
|
||||||
|
" - (hyphen)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
IndexingErrorInvalidLanguageCode(string *Filename, uint64_t LineNumber, char *Key, string Received)
|
||||||
|
{
|
||||||
|
ErrorFilenameAndLineNumber(Filename, LineNumber, S_ERROR, ED_INDEXING);
|
||||||
|
fprintf(stderr, "%s value %s%.*s%s contains invalid character(s)\n",
|
||||||
|
Key, ColourStrings[CS_MAGENTA_BOLD], (int)Received.Length, Received.Base, ColourStrings[CS_END]);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
IndexingErrorInvalidSubstring(string *Filename, uint64_t LineNumber, char *Key, string Received, string InvalidSubstring)
|
IndexingErrorInvalidSubstring(string *Filename, uint64_t LineNumber, char *Key, string Received, string InvalidSubstring)
|
||||||
{
|
{
|
||||||
|
@ -2481,6 +2498,23 @@ IsValidIdentifierCharacter(char C)
|
||||||
return ((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') || C == '_' || C == '-');
|
return ((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') || C == '_' || C == '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
IsValidLanguageCode(string S)
|
||||||
|
{
|
||||||
|
// TODO(matt): Add an upper limit to the length
|
||||||
|
bool Result = TRUE;
|
||||||
|
for(int i = 0; i < S.Length; ++i)
|
||||||
|
{
|
||||||
|
char C = S.Base[i];
|
||||||
|
if(!((C >= '0' && C <= '9') || (C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') || C == '-'))
|
||||||
|
{
|
||||||
|
Result = FALSE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
IsNumber(char C)
|
IsNumber(char C)
|
||||||
{
|
{
|
||||||
|
@ -11054,6 +11088,18 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string CCLang = CurrentProject->DefaultCCLang;
|
||||||
|
if(HMML.metadata.cc_lang)
|
||||||
|
{
|
||||||
|
CCLang = Wrap0(HMML.metadata.cc_lang);
|
||||||
|
if(!IsValidLanguageCode(CCLang))
|
||||||
|
{
|
||||||
|
IndexingErrorInvalidLanguageCode(&FilepathL, 0, "cc_lang", CCLang);
|
||||||
|
PrintValidLanguageCodeChars();
|
||||||
|
Result = RC_ERROR_HMML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string OutputLocation = {};
|
string OutputLocation = {};
|
||||||
if(!HMML.metadata.output)
|
if(!HMML.metadata.output)
|
||||||
{
|
{
|
||||||
|
@ -11230,8 +11276,17 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF
|
||||||
|
|
||||||
CopyStringToBuffer(&PlayerBuffers.Main,
|
CopyStringToBuffer(&PlayerBuffers.Main,
|
||||||
"<div class=\"cineraPlayerContainer\">\n"
|
"<div class=\"cineraPlayerContainer\">\n"
|
||||||
" <div class=\"video_container\" data-platform=\"%s\" data-videoId=\"%s\"></div>\n"
|
" <div class=\"video_container\" data-platform=\"%s\" data-videoId=\"%s\"", VODPlatformStrings[CollationBuffers->VODPlatform], HMML.metadata.id);
|
||||||
" <div class=\"markers_container %.*s\">\n", VODPlatformStrings[CollationBuffers->VODPlatform], HMML.metadata.id, (int)CurrentProject->Theme.Length, CurrentProject->Theme.Base);
|
|
||||||
|
if(CCLang.Length > 0)
|
||||||
|
{
|
||||||
|
CopyStringToBuffer(&PlayerBuffers.Main,
|
||||||
|
" data-ccLang=\"%.*s\"", (int)CCLang.Length, CCLang.Base);
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyStringToBuffer(&PlayerBuffers.Main,
|
||||||
|
"></div>\n"
|
||||||
|
" <div class=\"markers_container %.*s\">\n", (int)CurrentProject->Theme.Length, CurrentProject->Theme.Base);
|
||||||
|
|
||||||
if(N)
|
if(N)
|
||||||
{
|
{
|
||||||
|
|
|
@ -526,6 +526,7 @@ typedef struct project
|
||||||
string TemplatesDir;
|
string TemplatesDir;
|
||||||
string SearchTemplatePath;
|
string SearchTemplatePath;
|
||||||
string PlayerTemplatePath;
|
string PlayerTemplatePath;
|
||||||
|
string DefaultCCLang;
|
||||||
|
|
||||||
string BaseDir;
|
string BaseDir;
|
||||||
string BaseURL;
|
string BaseURL;
|
||||||
|
@ -918,6 +919,7 @@ InitTypeSpecs(void)
|
||||||
PushTypeSpecField(Root, FT_STRING, IDENT_COHOST, FALSE);
|
PushTypeSpecField(Root, FT_STRING, IDENT_COHOST, FALSE);
|
||||||
PushTypeSpecField(Root, FT_STRING, IDENT_CSS_PATH, TRUE);
|
PushTypeSpecField(Root, FT_STRING, IDENT_CSS_PATH, TRUE);
|
||||||
PushTypeSpecField(Root, FT_STRING, IDENT_DB_LOCATION, TRUE);
|
PushTypeSpecField(Root, FT_STRING, IDENT_DB_LOCATION, TRUE);
|
||||||
|
PushTypeSpecField(Root, FT_STRING, IDENT_DEFAULT_CC_LANG, TRUE);
|
||||||
PushTypeSpecField(Root, FT_STRING, IDENT_DEFAULT_MEDIUM, TRUE);
|
PushTypeSpecField(Root, FT_STRING, IDENT_DEFAULT_MEDIUM, TRUE);
|
||||||
PushTypeSpecField(Root, FT_STRING, IDENT_GENRE, TRUE);
|
PushTypeSpecField(Root, FT_STRING, IDENT_GENRE, TRUE);
|
||||||
PushTypeSpecField(Root, FT_STRING, IDENT_GLOBAL_SEARCH_DIR, TRUE);
|
PushTypeSpecField(Root, FT_STRING, IDENT_GLOBAL_SEARCH_DIR, TRUE);
|
||||||
|
@ -1018,6 +1020,7 @@ InitTypeSpecs(void)
|
||||||
config_type_spec *Project = PushTypeSpec(&Result, IDENT_PROJECT, TRUE);
|
config_type_spec *Project = PushTypeSpec(&Result, IDENT_PROJECT, TRUE);
|
||||||
PushTypeSpecField(Project, FT_STRING, IDENT_BASE_DIR, TRUE);
|
PushTypeSpecField(Project, FT_STRING, IDENT_BASE_DIR, TRUE);
|
||||||
PushTypeSpecField(Project, FT_STRING, IDENT_BASE_URL, TRUE);
|
PushTypeSpecField(Project, FT_STRING, IDENT_BASE_URL, TRUE);
|
||||||
|
PushTypeSpecField(Project, FT_STRING, IDENT_DEFAULT_CC_LANG, TRUE);
|
||||||
PushTypeSpecField(Project, FT_STRING, IDENT_DEFAULT_MEDIUM, TRUE);
|
PushTypeSpecField(Project, FT_STRING, IDENT_DEFAULT_MEDIUM, TRUE);
|
||||||
PushTypeSpecField(Project, FT_STRING, IDENT_GENRE, TRUE);
|
PushTypeSpecField(Project, FT_STRING, IDENT_GENRE, TRUE);
|
||||||
PushTypeSpecField(Project, FT_STRING, IDENT_HMML_DIR, TRUE);
|
PushTypeSpecField(Project, FT_STRING, IDENT_HMML_DIR, TRUE);
|
||||||
|
@ -3491,16 +3494,16 @@ PushPersonOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope
|
||||||
if(QuotemarkCount == 1)
|
if(QuotemarkCount == 1)
|
||||||
{
|
{
|
||||||
string Filepath = Wrap0(Pair->Position.Filename);
|
string Filepath = Wrap0(Pair->Position.Filename);
|
||||||
ConfigErrorField(&Filepath, Pair->Position.LineNumber, S_ERROR, IDENT_NAME,
|
ConfigErrorField(&Filepath, Pair->Position.LineNumber, S_ERROR, IDENT_NAME, &This->Name,
|
||||||
"contains unpaired quotation mark: ", &This->Name);
|
"contains unpaired quotation mark");
|
||||||
PushError(E, S_ERROR, 0, Pair->Key);
|
PushError(E, S_ERROR, 0, Pair->Key);
|
||||||
NamingError = TRUE;
|
NamingError = TRUE;
|
||||||
}
|
}
|
||||||
else if(QuotemarkCount > 2)
|
else if(QuotemarkCount > 2)
|
||||||
{
|
{
|
||||||
string Filepath = Wrap0(Pair->Position.Filename);
|
string Filepath = Wrap0(Pair->Position.Filename);
|
||||||
ConfigErrorField(&Filepath, Pair->Position.LineNumber, S_ERROR, IDENT_NAME,
|
ConfigErrorField(&Filepath, Pair->Position.LineNumber, S_ERROR, IDENT_NAME, &This->Name,
|
||||||
"contains more than one pair of quotation marks: ", &This->Name);
|
"contains more than one pair of quotation marks");
|
||||||
PushError(E, S_ERROR, 0, Pair->Key);
|
PushError(E, S_ERROR, 0, Pair->Key);
|
||||||
NamingError = TRUE;
|
NamingError = TRUE;
|
||||||
}
|
}
|
||||||
|
@ -3744,6 +3747,17 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc
|
||||||
switch(This->Key)
|
switch(This->Key)
|
||||||
{
|
{
|
||||||
// NOTE(matt): String
|
// NOTE(matt): String
|
||||||
|
case IDENT_DEFAULT_CC_LANG:
|
||||||
|
{
|
||||||
|
P->DefaultCCLang = ResolveString(C, E, ProjectTree, This, FALSE);
|
||||||
|
if(!IsValidLanguageCode(P->DefaultCCLang))
|
||||||
|
{
|
||||||
|
ConfigErrorField(&Filepath, This->Position.LineNumber, S_ERROR, This->Key, &P->DefaultCCLang,
|
||||||
|
"contains invalid character(s)");
|
||||||
|
PrintValidLanguageCodeChars();
|
||||||
|
PushError(E, S_ERROR, 0, This->Key);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
case IDENT_DEFAULT_MEDIUM:
|
case IDENT_DEFAULT_MEDIUM:
|
||||||
{ P->DefaultMedium = GetMediumFromProject(P, This->String); } break;
|
{ P->DefaultMedium = GetMediumFromProject(P, This->String); } break;
|
||||||
case IDENT_HMML_DIR:
|
case IDENT_HMML_DIR:
|
||||||
|
@ -4801,6 +4815,7 @@ PrintProject(config *C, project *P, typography *T, int Ancestors, int Indentatio
|
||||||
|
|
||||||
TypesetPair(T, Generation, IDENT_TITLE, P->Title, AvailableColumns);
|
TypesetPair(T, Generation, IDENT_TITLE, P->Title, AvailableColumns);
|
||||||
TypesetPair(T, Generation, IDENT_HTML_TITLE, P->HTMLTitle, AvailableColumns);
|
TypesetPair(T, Generation, IDENT_HTML_TITLE, P->HTMLTitle, AvailableColumns);
|
||||||
|
TypesetPair(T, Generation, IDENT_DEFAULT_CC_LANG, P->DefaultCCLang.Length ? P->DefaultCCLang : EmptyString(), AvailableColumns);
|
||||||
TypesetPair(T, Generation, IDENT_DEFAULT_MEDIUM, P->DefaultMedium ? P->DefaultMedium->ID : EmptyString(), AvailableColumns);
|
TypesetPair(T, Generation, IDENT_DEFAULT_MEDIUM, P->DefaultMedium ? P->DefaultMedium->ID : EmptyString(), AvailableColumns);
|
||||||
|
|
||||||
TypesetPair(T, Generation, IDENT_HMML_DIR, P->HMMLDir, AvailableColumns);
|
TypesetPair(T, Generation, IDENT_HMML_DIR, P->HMMLDir, AvailableColumns);
|
||||||
|
|
|
@ -965,10 +965,19 @@ Player.prototype.onPlatformReady = function() {
|
||||||
{
|
{
|
||||||
this.videoContainer.style.position = "relative";
|
this.videoContainer.style.position = "relative";
|
||||||
this.videoContainer.style.alignSelf = "unset";
|
this.videoContainer.style.alignSelf = "unset";
|
||||||
this.platformPlayer = new Vimeo.Player(platformPlayerDiv.id, {
|
|
||||||
|
var CallData = {
|
||||||
id: this.videoContainer.getAttribute("data-videoId"),
|
id: this.videoContainer.getAttribute("data-videoId"),
|
||||||
title: false,
|
title: false,
|
||||||
});
|
};
|
||||||
|
var CCLang = this.videoContainer.getAttribute("data-ccLang");
|
||||||
|
if(CCLang != null)
|
||||||
|
{
|
||||||
|
CallData.texttrack = CCLang;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.platformPlayer = new Vimeo.Player(platformPlayerDiv.id, CallData);
|
||||||
|
|
||||||
this.platformPlayer.ready()
|
this.platformPlayer.ready()
|
||||||
.then(this.onPlatformPlayerReady.bind(this));
|
.then(this.onPlatformPlayerReady.bind(this));
|
||||||
this.platformPlayer.on("playbackratechange", this.onVimeoPlayerPlaybackRateChange.bind(this));
|
this.platformPlayer.on("playbackratechange", this.onVimeoPlayerPlaybackRateChange.bind(this));
|
||||||
|
@ -980,7 +989,7 @@ Player.prototype.onPlatformReady = function() {
|
||||||
} break;
|
} break;
|
||||||
case vod_platform.YOUTUBE:
|
case vod_platform.YOUTUBE:
|
||||||
{
|
{
|
||||||
this.platformPlayer = new YT.Player(platformPlayerDiv.id, {
|
var CallData = {
|
||||||
videoId: this.videoContainer.getAttribute("data-videoId"),
|
videoId: this.videoContainer.getAttribute("data-videoId"),
|
||||||
width: this.videoContainer.offsetWidth,
|
width: this.videoContainer.offsetWidth,
|
||||||
height: this.videoContainer.offsetWidth / 16 * 9,
|
height: this.videoContainer.offsetWidth / 16 * 9,
|
||||||
|
@ -990,7 +999,15 @@ Player.prototype.onPlatformReady = function() {
|
||||||
"onStateChange": this.onYouTubePlayerStateChange.bind(this),
|
"onStateChange": this.onYouTubePlayerStateChange.bind(this),
|
||||||
"onPlaybackRateChange": this.onYouTubePlayerPlaybackRateChange.bind(this)
|
"onPlaybackRateChange": this.onYouTubePlayerPlaybackRateChange.bind(this)
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
var CCLang = this.videoContainer.getAttribute("data-ccLang");
|
||||||
|
if(CCLang != null)
|
||||||
|
{
|
||||||
|
CallData.cc_lang_pref = CCLang;
|
||||||
|
CallData.cc_load_policy = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.platformPlayer = new YT.Player(platformPlayerDiv.id, CallData);
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,7 @@ typedef struct {
|
||||||
char* template;
|
char* template;
|
||||||
char* medium;
|
char* medium;
|
||||||
char* number;
|
char* number;
|
||||||
|
char* cc_lang;
|
||||||
|
|
||||||
HMML_Credit* credits;
|
HMML_Credit* credits;
|
||||||
size_t credit_count;
|
size_t credit_count;
|
||||||
|
@ -737,6 +738,7 @@ static void _hmml_parse_video(struct _hmml_parser* p)
|
||||||
{ HSTR("medium") , &p->out.metadata.medium },
|
{ HSTR("medium") , &p->out.metadata.medium },
|
||||||
{ HSTR("number") , &p->out.metadata.number },
|
{ HSTR("number") , &p->out.metadata.number },
|
||||||
{ HSTR("output") , &p->out.metadata.output },
|
{ HSTR("output") , &p->out.metadata.output },
|
||||||
|
{ HSTR("cc_lang") , &p->out.metadata.cc_lang },
|
||||||
};
|
};
|
||||||
|
|
||||||
for(;;) {
|
for(;;) {
|
||||||
|
@ -822,7 +824,7 @@ void hmml_free(HMML_Output* out)
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct HMML_Version hmml_version = {
|
const struct HMML_Version hmml_version = {
|
||||||
2, 0, 13
|
2, 0, 14
|
||||||
};
|
};
|
||||||
|
|
||||||
#undef HSTX
|
#undef HSTX
|
||||||
|
|
Loading…
Reference in New Issue