From 406539417c894954ef88ecc2fc28c26ad01bbba6 Mon Sep 17 00:00:00 2001 From: vortex Date: Sat, 24 Apr 2010 15:22:01 +0000 Subject: [PATCH] simple video subtitles support, defined in external text files, added a couple of cvars controlling subtitles (cl_video_*). Added video scaling (video will not be drawn fullscreen but less size and centered) and brightness cvar. Added videoplaying() builtin for menuvm and csqc. Changed isdemo() function to be shared between menuvm and csqc (since it is same). Subtitles uses NOTIFY font. git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@10099 d7cf8633-e32d-0410-b094-e92efae38249 --- cl_video.c | 236 +++++++++++++++++++++++++++++++++++++++++++++++++--- cl_video.h | 18 +++- clvm_cmds.c | 9 +- mvm_cmds.c | 11 +-- prvm_cmds.c | 22 ++++- prvm_cmds.h | 4 + svvm_cmds.c | 2 + 7 files changed, 270 insertions(+), 32 deletions(-) diff --git a/cl_video.c b/cl_video.c index fcfd0d0a..1328322d 100644 --- a/cl_video.c +++ b/cl_video.c @@ -4,6 +4,13 @@ #include "cl_video.h" #include "dpvsimpledecode.h" +// cvars +cvar_t cl_video_subtitles = {CVAR_SAVE, "cl_video_subtitles", "0", "show subtitles for videos (if they are presented)"}; +cvar_t cl_video_subtitles_lines = {CVAR_SAVE, "cl_video_subtitles_lines", "4", "how many lines to occupy for subtitles"}; +cvar_t cl_video_subtitles_textsize = {CVAR_SAVE, "cl_video_subtitles_textsize", "16", "textsize for subtitles"}; +cvar_t cl_video_scale = {CVAR_SAVE, "cl_video_scale", "1", "scale of video, 1 = fullscreen, 0.75 - 3/4 of screen etc."}; +cvar_t cl_video_brightness = {CVAR_SAVE, "cl_video_brightness", "1", "brightness of video, 1 = fullbright, 0.75 - 3/4 etc."}; + // constants (and semi-constants) static int cl_videormask; static int cl_videobmask; @@ -87,7 +94,75 @@ static qboolean WakeVideo( clvideo_t * video ) return true; } -static clvideo_t* OpenVideo( clvideo_t *video, const char *filename, const char *name, int owner ) +static void LoadSubtitles( clvideo_t *video, const char *subtitlesfile ) +{ + char *subtitle_text, *data; + float subtime, sublen; + int numsubs = 0; + + subtitle_text = (char *)FS_LoadFile(subtitlesfile, cls.permanentmempool, false, NULL); + if (!subtitle_text) + { + Con_DPrintf( "LoadSubtitles: can't open subtitle file '%s'!\n", subtitlesfile ); + return; + } + + // parse subtitle_text + // line is: x y "text" where + // x - start time + // y - seconds last (if 0 - last thru next sub, if negative - last to next sub - this amount of seconds) + + data = subtitle_text; + for (;;) + { + if (!COM_ParseToken_QuakeC(&data, false)) + break; + subtime = atof( com_token ); + if (!COM_ParseToken_QuakeC(&data, false)) + break; + sublen = atof( com_token ); + if (!COM_ParseToken_QuakeC(&data, false)) + break; + if (!com_token[0]) + continue; + // check limits + if (video->subtitles == CLVIDEO_MAX_SUBTITLES) + { + Con_Printf("WARNING: CLVIDEO_MAX_SUBTITLES = %i reached when reading subtitles from '%s'\n", CLVIDEO_MAX_SUBTITLES, subtitlesfile); + break; + } + // add a sub + video->subtitle_text[numsubs] = (char *) Mem_Alloc(cls.permanentmempool, strlen(com_token) + 1); + memcpy(video->subtitle_text[numsubs], com_token, strlen(com_token) + 1); + video->subtitle_start[numsubs] = subtime; + video->subtitle_end[numsubs] = sublen; + if (numsubs > 0) // make true len for prev sub, autofix overlapping subtitles + { + if (video->subtitle_end[numsubs-1] <= 0) + video->subtitle_end[numsubs-1] = max(video->subtitle_start[numsubs-1], video->subtitle_start[numsubs] - video->subtitle_end[numsubs-1]); + else + video->subtitle_end[numsubs-1] = min(video->subtitle_start[numsubs-1] + video->subtitle_end[numsubs-1], video->subtitle_start[numsubs]); + } + numsubs++; + // todo: check timing for consistency? + } + if (numsubs > 0) // make true len for prev sub, autofix overlapping subtitles + { + if (video->subtitle_end[numsubs-1] <= 0) + video->subtitle_end[numsubs-1] = 99999999; // fixme: make it end when video ends? + else + video->subtitle_end[numsubs-1] = min(video->subtitle_start[numsubs-1] + video->subtitle_end[numsubs-1], video->subtitle_start[numsubs]); + } + Z_Free( subtitle_text ); + video->subtitles = numsubs; +/* + Con_Printf( "video->subtitles: %i\n", video->subtitles ); + for (numsubs = 0; numsubs < video->subtitles; numsubs++) + Con_Printf( " %03.2f %03.2f : %s\n", video->subtitle_start[numsubs], video->subtitle_end[numsubs], video->subtitle_text[numsubs] ); +*/ +} + +static clvideo_t* OpenVideo( clvideo_t *video, const char *filename, const char *name, int owner, const char *subtitlesfile ) { strlcpy( video->filename, filename, sizeof(video->filename) ); video->ownertag = owner; @@ -102,16 +177,21 @@ static clvideo_t* OpenVideo( clvideo_t *video, const char *filename, const char video->framenum = -1; video->framerate = dpvsimpledecode_getframerate( video->stream ); video->lasttime = realtime; + video->subtitles = 0; video->cpif.width = dpvsimpledecode_getwidth( video->stream ); video->cpif.height = dpvsimpledecode_getheight( video->stream ); video->imagedata = Mem_Alloc( cls.permanentmempool, video->cpif.width * video->cpif.height * cl_videobytesperpixel ); LinkVideoTexture( video ); + // VorteX: load simple subtitle_text file + if (subtitlesfile[0]) + LoadSubtitles( video, subtitlesfile ); + return video; } -clvideo_t* CL_OpenVideo( const char *filename, const char *name, int owner ) +clvideo_t* CL_OpenVideo( const char *filename, const char *name, int owner, const char *subtitlesfile ) { clvideo_t *video; // sanity check @@ -125,7 +205,7 @@ clvideo_t* CL_OpenVideo( const char *filename, const char *name, int owner ) Con_Printf( "CL_OpenVideo: unable to open video \"%s\" - video limit reached\n", filename ); return NULL; } - video = OpenVideo( video, filename, name, owner ); + video = OpenVideo( video, filename, name, owner, subtitlesfile ); // expand the active range to include the new entry if (video) { cl_num_videos = max(cl_num_videos, (int)(video - cl_videos) + 1); @@ -190,13 +270,20 @@ void CL_RestartVideo( clvideo_t *video ) void CL_CloseVideo( clvideo_t * video ) { + int i; + if( !video || video->state == CLVIDEO_UNUSED ) return; if( !video->suspended || video->state != CLVIDEO_FIRSTFRAME ) dpvsimpledecode_close( video->stream ); - if( !video->suspended ) { + if( !video->suspended ) UnlinkVideoTexture( video ); + if (video->subtitles) + { + for (i = 0; i < video->subtitles; i++) + Z_Free( video->subtitle_text[i] ); + video->subtitles = 0; } video->state = CLVIDEO_UNUSED; @@ -271,22 +358,135 @@ void CL_PurgeOwner( int owner ) CL_CloseVideo( &cl_videos[ i ] ); } +typedef struct +{ + dp_font_t *font; + float x; + float y; + float width; + float height; + float alignment; // 0 = left, 0.5 = center, 1 = right + float fontsize; + float textalpha; +} +cl_video_subtitle_info_t; + +float CL_DrawVideo_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth) +{ + cl_video_subtitle_info_t *si = (cl_video_subtitle_info_t *) passthrough; + + if(w == NULL) + return si->fontsize * si->font->maxwidth; + if(maxWidth >= 0) + return DrawQ_TextWidth_UntilWidth(w, length, si->fontsize, si->fontsize, false, si->font, -maxWidth); // -maxWidth: we want at least one char + else if(maxWidth == -1) + return DrawQ_TextWidth(w, *length, si->fontsize, si->fontsize, false, si->font); + else + return 0; +} + +int CL_DrawVideo_DisplaySubtitleLine(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation) +{ + cl_video_subtitle_info_t *si = (cl_video_subtitle_info_t *) passthrough; + + int x = (int) (si->x + (si->width - width) * si->alignment); + if (length > 0) + DrawQ_String(x, si->y, line, length, si->fontsize, si->fontsize, 1.0, 1.0, 1.0, si->textalpha, 0, NULL, false, si->font); + si->y += si->fontsize; + return 1; +} + int cl_videoplaying = false; // old, but still supported void CL_DrawVideo(void) { - if (cl_videoplaying) - DrawQ_Pic(0, 0, &CL_GetVideoBySlot( 0 )->cpif, vid_conwidth.integer, vid_conheight.integer, 1, 1, 1, 1, 0); + clvideo_t *video; + float videotime; + cl_video_subtitle_info_t si; + int i; + + if (!cl_videoplaying) + return; + + video = CL_GetVideoBySlot( 0 ); + + // fix cvars + if (cl_video_scale.value <= 0 || cl_video_scale.value > 1) + Cvar_SetValueQuick( &cl_video_scale, 1); + if (cl_video_brightness.value <= 0 || cl_video_brightness.value > 10) + Cvar_SetValueQuick( &cl_video_brightness, 1); + +#if 0 + // enable video-only polygon stipple (of global stipple is not active) + if (qglPolygonStipple && !scr_stipple.integer) + { + GLubyte stipple[128]; + int i, s, width, parts; + + s = 1; + parts = (s & 007); + width = (s & 070) >> 3; + qglEnable(GL_POLYGON_STIPPLE);CHECKGLERROR // 0x0B42 + for(i = 0; i < 128; ++i) + { + int line = i/4; + stipple[i] = ((line >> width) & ((1 << parts) - 1)) ? 0x00 : 0xFF; + } + qglPolygonStipple(stipple);CHECKGLERROR + } +#endif + + // draw video + if (cl_video_scale.value == 1) + DrawQ_Pic(0, 0, &video->cpif, vid_conwidth.integer, vid_conheight.integer, cl_video_brightness.value, cl_video_brightness.value, cl_video_brightness.value, 1, 0); + else + { + DrawQ_Fill(0, 0, vid_conwidth.integer, vid_conheight.integer, 0, 0, 0, 1, 0); + DrawQ_Pic((int)(vid_conwidth.integer * (1 - cl_video_scale.value) * 0.5), (int)(vid_conheight.integer * (1 - cl_video_scale.value) * 0.5), &video->cpif, (int)(vid_conwidth.integer * cl_video_scale.value), (int)(vid_conheight.integer * cl_video_scale.value), cl_video_brightness.value, cl_video_brightness.value, cl_video_brightness.value, 1, 0); + } + + +#if 0 + // disable video-only stipple + if (qglPolygonStipple && !scr_stipple.integer) + { + qglDisable(GL_POLYGON_STIPPLE);CHECKGLERROR + } +#endif + + // VorteX: draw subtitle_text + if (!video->subtitles || !cl_video_subtitles.integer) + return; + + // find current subtitle + videotime = realtime - video->starttime; + for (i = 0; i < video->subtitles; i++) + { + if (videotime >= video->subtitle_start[i] && videotime <= video->subtitle_end[i]) + { + // found, draw it + si.font = FONT_NOTIFY; + si.x = vid_conwidth.integer * 0.1; + si.y = vid_conheight.integer - (max(1, cl_video_subtitles_lines.integer) * cl_video_subtitles_textsize.value); + si.width = vid_conwidth.integer * 0.8; + si.height = max(1, cl_video_subtitles_lines.integer) * cl_video_subtitles_textsize.value; + si.alignment = 0.5; + si.fontsize = cl_video_subtitles_textsize.value; + si.textalpha = min(1, (videotime - video->subtitle_start[i])/0.5) * min(1, ((video->subtitle_end[i] - videotime)/0.3)); // fade in and fade out + COM_Wordwrap(video->subtitle_text[i], strlen(video->subtitle_text[i]), 0, si.width, CL_DrawVideo_WordWidthFunc, &si, CL_DrawVideo_DisplaySubtitleLine, &si); + break; + } + } } -void CL_VideoStart(char *filename) +void CL_VideoStart(char *filename, const char *subtitlesfile) { Host_StartVideo(); if( cl_videos->state != CLVIDEO_UNUSED ) CL_CloseVideo( cl_videos ); // already contains video/ - if( !OpenVideo( cl_videos, filename, va( CLDYNTEXTUREPREFIX "%s", filename ), 0 ) ) + if( !OpenVideo( cl_videos, filename, va( CLDYNTEXTUREPREFIX "%s", filename ), 0, subtitlesfile ) ) return; // expand the active range to include the new entry cl_num_videos = max(cl_num_videos, 1); @@ -316,18 +516,24 @@ void CL_VideoStop(void) static void CL_PlayVideo_f(void) { - char name[MAX_QPATH]; + char name[MAX_QPATH], subtitlesfile[MAX_QPATH]; Host_StartVideo(); - if (Cmd_Argc() != 2) + if (Cmd_Argc() < 2) { - Con_Print("usage: playvideo \nplays video named video/.dpv\n"); + Con_Print("usage: playvideo [custom_subtitles_file]\nplays video named video/.dpv\nif custom subtitles file is not presented\nit tries video/.sub"); return; } dpsnprintf(name, sizeof(name), "video/%s.dpv", Cmd_Argv(1)); - CL_VideoStart(name); + if ( Cmd_Argc() > 2) + CL_VideoStart(name, Cmd_Argv(2)); + else + { + dpsnprintf(subtitlesfile, sizeof(subtitlesfile), "video/%s.dpsubs", Cmd_Argv(1)); + CL_VideoStart(name, subtitlesfile); + } } static void CL_StopVideo_f(void) @@ -377,6 +583,12 @@ void CL_Video_Init( void ) Cmd_AddCommand( "playvideo", CL_PlayVideo_f, "play a .dpv video file" ); Cmd_AddCommand( "stopvideo", CL_StopVideo_f, "stop playing a .dpv video file" ); + Cvar_RegisterVariable(&cl_video_subtitles); + Cvar_RegisterVariable(&cl_video_subtitles_lines); + Cvar_RegisterVariable(&cl_video_subtitles_textsize); + Cvar_RegisterVariable(&cl_video_scale); + Cvar_RegisterVariable(&cl_video_brightness); + R_RegisterModule( "CL_Video", cl_video_start, cl_video_shutdown, cl_video_newmap ); } diff --git a/cl_video.h b/cl_video.h index d98c0087..9e3c47c0 100644 --- a/cl_video.h +++ b/cl_video.h @@ -21,6 +21,14 @@ typedef enum clvideostate_e CLVIDEO_STATECOUNT } clvideostate_t; +#define CLVIDEO_MAX_SUBTITLES 512 + +extern cvar_t cl_video_subtitles; +extern cvar_t cl_video_subtitles_lines; +extern cvar_t cl_video_subtitles_textsize; +extern cvar_t cl_video_scale; +extern cvar_t cl_video_brightness; + typedef struct clvideo_s { int ownertag; @@ -37,6 +45,12 @@ typedef struct clvideo_s cachepic_t cpif; + // VorteX: subtitles array + int subtitles; + char *subtitle_text[CLVIDEO_MAX_SUBTITLES]; + float subtitle_start[CLVIDEO_MAX_SUBTITLES]; + float subtitle_end[CLVIDEO_MAX_SUBTITLES]; + // if a video is suspended, it is automatically paused (else we'd still have to process the frames) // used to determine whether the video's resources should be freed or not @@ -47,7 +61,7 @@ typedef struct clvideo_s char filename[MAX_QPATH]; } clvideo_t; -clvideo_t* CL_OpenVideo( const char *filename, const char *name, int owner ); +clvideo_t* CL_OpenVideo( const char *filename, const char *name, int owner, const char *subtitlesfile ); clvideo_t* CL_GetVideoByName( const char *name ); void CL_SetVideoState( clvideo_t *video, clvideostate_t state ); void CL_RestartVideo( clvideo_t *video ); @@ -63,7 +77,7 @@ void CL_Video_Shutdown( void ); extern int cl_videoplaying; void CL_DrawVideo( void ); -void CL_VideoStart( char *filename ); +void CL_VideoStart( char *filename, const char *subtitlesfile ); void CL_VideoStop( void ); // new function used for fullscreen videos diff --git a/clvm_cmds.c b/clvm_cmds.c index f328ee11..2c6fb4fe 100644 --- a/clvm_cmds.c +++ b/clvm_cmds.c @@ -1284,13 +1284,6 @@ static void VM_CL_getplayerkey (void) PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(t); } -//#349 float() isdemo (EXT_CSQC) -static void VM_CL_isdemo (void) -{ - VM_SAFEPARMCOUNT(0, VM_CL_isdemo); - PRVM_G_FLOAT(OFS_RETURN) = cls.demoplayback; -} - //#351 void(vector origin, vector forward, vector right, vector up) SetListener (EXT_CSQC) static void VM_CL_setlistener (void) { @@ -4213,7 +4206,7 @@ VM_CL_setlistener, // #351 void(vector origin, vector forward, vector right, VM_CL_registercmd, // #352 void(string cmdname) registercommand (EXT_CSQC) VM_wasfreed, // #353 float(entity ent) wasfreed (EXT_CSQC) (should be availabe on server too) VM_CL_serverkey, // #354 string(string key) serverkey (EXT_CSQC) -NULL, // #355 +VM_CL_videoplaying, // #355 NULL, // #356 NULL, // #357 NULL, // #358 diff --git a/mvm_cmds.c b/mvm_cmds.c index d21c0819..9f6f6080 100644 --- a/mvm_cmds.c +++ b/mvm_cmds.c @@ -746,13 +746,6 @@ static void VM_M_getmousepos(void) VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_mouse_x * vid_conwidth.integer / vid.width, in_mouse_y * vid_conheight.integer / vid.height, 0); } -//#349 float() isdemo (EXT_CSQC) -static void VM_M_isdemo (void) -{ - VM_SAFEPARMCOUNT(0, VM_M_isdemo); - PRVM_G_FLOAT(OFS_RETURN) = cls.demoplayback; -} - prvm_builtin_t vm_m_builtins[] = { NULL, // #0 NULL function (not callable) VM_checkextension, // #1 @@ -1130,13 +1123,13 @@ NULL, // #345 NULL, // #346 NULL, // #347 NULL, // #348 -VM_M_isdemo, // #349 +VM_CL_isdemo, // #349 NULL, // #350 NULL, // #351 NULL, // #352 NULL, // #353 NULL, // #354 -NULL, // #355 +VM_CL_videoplaying, // #355 NULL, // #356 NULL, // #357 NULL, // #358 diff --git a/prvm_cmds.c b/prvm_cmds.c index 55caaa57..83eb617c 100644 --- a/prvm_cmds.c +++ b/prvm_cmds.c @@ -3800,7 +3800,7 @@ void VM_cin_open( void ) VM_CheckEmptyString( file ); VM_CheckEmptyString( name ); - if( CL_OpenVideo( file, name, MENUOWNER ) ) + if( CL_OpenVideo( file, name, MENUOWNER, "" ) ) PRVM_G_FLOAT( OFS_RETURN ) = 1; else PRVM_G_FLOAT( OFS_RETURN ) = 0; @@ -5680,6 +5680,26 @@ void VM_SV_getextresponse (void) } } +/* +========= +Common functions between menu.dat and clsprogs +========= +*/ + +//#349 float() isdemo +void VM_CL_isdemo (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_isdemo); + PRVM_G_FLOAT(OFS_RETURN) = cls.demoplayback; +} + +//#355 float() videoplaying +void VM_CL_videoplaying (void) +{ + VM_SAFEPARMCOUNT(0, VM_CL_videoplaying); + PRVM_G_FLOAT(OFS_RETURN) = cl_videoplaying; +} + /* ========= VM_M_callfunction diff --git a/prvm_cmds.h b/prvm_cmds.h index 063c0fa0..c4f330ac 100644 --- a/prvm_cmds.h +++ b/prvm_cmds.h @@ -448,6 +448,10 @@ void VM_cvar_description(void); void VM_CL_getextresponse (void); void VM_SV_getextresponse (void); +// Common functions between menu.dat and clsprogs +void VM_CL_isdemo (void); +void VM_CL_videoplaying (void); + void VM_isfunction(void); void VM_callfunction(void); diff --git a/svvm_cmds.c b/svvm_cmds.c index 224d8095..04cffd6b 100644 --- a/svvm_cmds.c +++ b/svvm_cmds.c @@ -134,6 +134,8 @@ char *vm_sv_extensions = "DP_SND_SETPARAMS " "DP_SND_STEREOWAV " "DP_SND_GETSOUNDTIME " +"DP_VIDEO_DPV " +"DP_VIDEO_SUBTITLES " "DP_SOLIDCORPSE " "DP_SPRITE32 " "DP_SV_BOTCLIENT " -- 2.39.2