2011-09-17 16:52:19 +03:00
/*
* Copyright ( c ) 2011 Baptiste Coudurier
* Copyright ( c ) 2011 Stefano Sabatini
2012-11-29 05:28:37 +03:00
* Copyright ( c ) 2012 Clément Bœsch
2011-09-17 16:52:19 +03:00
*
* This file is part of FFmpeg .
*
* FFmpeg is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* FFmpeg is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
/**
* @ file
* Libass subtitles burning filter .
*
* @ see { http : //www.matroska.org/technical/specs/subtitles/ssa.html}
*/
# include <ass/ass.h>
2012-11-29 05:28:37 +03:00
# include "config.h"
# if CONFIG_SUBTITLES_FILTER
# include "libavcodec / avcodec.h"
# include "libavformat / avformat.h"
# endif
2011-09-17 16:52:19 +03:00
# include "libavutil/avstring.h"
# include "libavutil/imgutils.h"
2012-03-19 21:28:56 +03:00
# include "libavutil/opt.h"
# include "libavutil/parseutils.h"
2011-09-17 16:52:19 +03:00
# include "drawutils.h"
# include "avfilter.h"
2012-06-22 15:33:09 +03:00
# include "internal.h"
2012-06-21 12:07:35 +03:00
# include "formats.h"
# include "video.h"
2011-09-17 16:52:19 +03:00
typedef struct {
2012-03-19 21:28:56 +03:00
const AVClass * class ;
2011-09-17 16:52:19 +03:00
ASS_Library * library ;
ASS_Renderer * renderer ;
ASS_Track * track ;
char * filename ;
2012-12-31 13:15:59 +03:00
char * charenc ;
2014-05-01 13:51:17 +03:00
int stream_index ;
2011-09-17 16:52:19 +03:00
uint8_t rgba_map [ 4 ] ;
int pix_step [ 4 ] ; ///< steps per pixel for each plane of the main output
2012-03-22 22:59:36 +03:00
int original_w , original_h ;
2014-09-11 22:10:43 +03:00
int shaping ;
2012-03-28 16:51:13 +03:00
FFDrawContext draw ;
2011-09-17 16:52:19 +03:00
} AssContext ;
2012-03-19 21:28:56 +03:00
# define OFFSET(x) offsetof(AssContext, x)
2012-08-13 14:40:01 +03:00
# define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
2012-03-19 21:28:56 +03:00
2012-12-31 13:07:29 +03:00
# define COMMON_OPTIONS \
{ " filename " , " set the filename of file to read " , OFFSET ( filename ) , AV_OPT_TYPE_STRING , { . str = NULL } , CHAR_MIN , CHAR_MAX , FLAGS } , \
{ " f " , " set the filename of file to read " , OFFSET ( filename ) , AV_OPT_TYPE_STRING , { . str = NULL } , CHAR_MIN , CHAR_MAX , FLAGS } , \
{ " original_size " , " set the size of the original video (used to scale fonts) " , OFFSET ( original_w ) , AV_OPT_TYPE_IMAGE_SIZE , { . str = NULL } , CHAR_MIN , CHAR_MAX , FLAGS } , \
2012-03-19 21:28:56 +03:00
2011-09-17 16:52:19 +03:00
/* libass supports a log level ranging from 0 to 7 */
2012-09-16 03:59:43 +03:00
static const int ass_libavfilter_log_level_map [ ] = {
2014-09-11 22:10:03 +03:00
[ 0 ] = AV_LOG_FATAL , /* MSGL_FATAL */
[ 1 ] = AV_LOG_ERROR , /* MSGL_ERR */
[ 2 ] = AV_LOG_WARNING , /* MSGL_WARN */
[ 3 ] = AV_LOG_WARNING , /* <undefined> */
[ 4 ] = AV_LOG_INFO , /* MSGL_INFO */
[ 5 ] = AV_LOG_INFO , /* <undefined> */
[ 6 ] = AV_LOG_VERBOSE , /* MSGL_V */
[ 7 ] = AV_LOG_DEBUG , /* MSGL_DBG2 */
2011-09-17 16:52:19 +03:00
} ;
static void ass_log ( int ass_level , const char * fmt , va_list args , void * ctx )
{
2014-09-11 22:09:39 +03:00
const int ass_level_clip = av_clip ( ass_level , 0 ,
FF_ARRAY_ELEMS ( ass_libavfilter_log_level_map ) - 1 ) ;
const int level = ass_libavfilter_log_level_map [ ass_level_clip ] ;
2011-09-17 16:52:19 +03:00
av_vlog ( ctx , level , fmt , args ) ;
av_log ( ctx , level , " \n " ) ;
}
2013-04-12 12:13:33 +03:00
static av_cold int init ( AVFilterContext * ctx )
2011-09-17 16:52:19 +03:00
{
AssContext * ass = ctx - > priv ;
2012-10-15 11:06:52 +03:00
if ( ! ass - > filename ) {
2011-09-17 16:52:19 +03:00
av_log ( ctx , AV_LOG_ERROR , " No filename provided! \n " ) ;
return AVERROR ( EINVAL ) ;
}
ass - > library = ass_library_init ( ) ;
if ( ! ass - > library ) {
av_log ( ctx , AV_LOG_ERROR , " Could not initialize libass. \n " ) ;
return AVERROR ( EINVAL ) ;
}
ass_set_message_cb ( ass - > library , ass_log , ctx ) ;
ass - > renderer = ass_renderer_init ( ass - > library ) ;
if ( ! ass - > renderer ) {
av_log ( ctx , AV_LOG_ERROR , " Could not initialize libass renderer. \n " ) ;
return AVERROR ( EINVAL ) ;
}
return 0 ;
}
static av_cold void uninit ( AVFilterContext * ctx )
{
AssContext * ass = ctx - > priv ;
if ( ass - > track )
ass_free_track ( ass - > track ) ;
if ( ass - > renderer )
ass_renderer_done ( ass - > renderer ) ;
if ( ass - > library )
ass_library_done ( ass - > library ) ;
}
static int query_formats ( AVFilterContext * ctx )
{
2012-06-21 12:07:35 +03:00
ff_set_common_formats ( ctx , ff_draw_supported_pixel_formats ( 0 ) ) ;
2011-09-17 16:52:19 +03:00
return 0 ;
}
static int config_input ( AVFilterLink * inlink )
{
AssContext * ass = inlink - > dst - > priv ;
2012-03-28 16:51:13 +03:00
ff_draw_init ( & ass - > draw , inlink - > format , 0 ) ;
2011-09-17 16:52:19 +03:00
ass_set_frame_size ( ass - > renderer , inlink - > w , inlink - > h ) ;
2012-03-22 22:59:36 +03:00
if ( ass - > original_w & & ass - > original_h )
ass_set_aspect_ratio ( ass - > renderer , ( double ) inlink - > w / inlink - > h ,
( double ) ass - > original_w / ass - > original_h ) ;
2014-09-11 22:10:43 +03:00
if ( ass - > shaping ! = - 1 )
ass_set_shaper ( ass - > renderer , ass - > shaping ) ;
2011-09-17 16:52:19 +03:00
return 0 ;
}
/* libass stores an RGBA color in the format RRGGBBTT, where TT is the transparency level */
# define AR(c) ( (c)>>24)
# define AG(c) (((c)>>16)&0xFF)
# define AB(c) (((c)>>8) &0xFF)
# define AA(c) ((0xFF-c) &0xFF)
2013-03-10 03:30:30 +03:00
static void overlay_ass_image ( AssContext * ass , AVFrame * picref ,
2011-09-17 16:52:19 +03:00
const ASS_Image * image )
{
for ( ; image ; image = image - > next ) {
uint8_t rgba_color [ ] = { AR ( image - > color ) , AG ( image - > color ) , AB ( image - > color ) , AA ( image - > color ) } ;
2012-03-28 16:51:13 +03:00
FFDrawColor color ;
ff_draw_color ( & ass - > draw , & color , rgba_color ) ;
ff_blend_mask ( & ass - > draw , & color ,
picref - > data , picref - > linesize ,
2013-03-10 03:30:30 +03:00
picref - > width , picref - > height ,
2012-03-28 16:51:13 +03:00
image - > bitmap , image - > stride , image - > w , image - > h ,
3 , 0 , image - > dst_x , image - > dst_y ) ;
2011-09-17 16:52:19 +03:00
}
}
2013-03-10 03:30:30 +03:00
static int filter_frame ( AVFilterLink * inlink , AVFrame * picref )
2011-09-17 16:52:19 +03:00
{
AVFilterContext * ctx = inlink - > dst ;
AVFilterLink * outlink = ctx - > outputs [ 0 ] ;
AssContext * ass = ctx - > priv ;
int detect_change = 0 ;
double time_ms = picref - > pts * av_q2d ( inlink - > time_base ) * 1000 ;
ASS_Image * image = ass_render_frame ( ass - > renderer , ass - > track ,
time_ms , & detect_change ) ;
if ( detect_change )
av_log ( ctx , AV_LOG_DEBUG , " Change happened at time ms:%f \n " , time_ms ) ;
2012-03-28 16:51:13 +03:00
overlay_ass_image ( ass , picref , image ) ;
2011-09-17 16:52:19 +03:00
2012-11-29 04:34:09 +03:00
return ff_filter_frame ( outlink , picref ) ;
2011-09-17 16:52:19 +03:00
}
2012-11-28 22:01:59 +03:00
static const AVFilterPad ass_inputs [ ] = {
{
. name = " default " ,
. type = AVMEDIA_TYPE_VIDEO ,
2012-11-29 04:34:09 +03:00
. filter_frame = filter_frame ,
2012-11-28 22:01:59 +03:00
. config_props = config_input ,
2013-03-10 03:30:30 +03:00
. needs_writable = 1 ,
2012-11-28 22:01:59 +03:00
} ,
{ NULL }
} ;
static const AVFilterPad ass_outputs [ ] = {
{
. name = " default " ,
. type = AVMEDIA_TYPE_VIDEO ,
} ,
{ NULL }
} ;
2012-11-29 05:28:37 +03:00
# if CONFIG_ASS_FILTER
2012-12-31 13:07:29 +03:00
static const AVOption ass_options [ ] = {
COMMON_OPTIONS
2014-09-11 22:10:43 +03:00
{ " shaping " , " set shaping engine " , OFFSET ( shaping ) , AV_OPT_TYPE_INT , { . i64 = - 1 } , - 1 , 1 , FLAGS , " shaping_mode " } ,
{ " auto " , NULL , 0 , AV_OPT_TYPE_CONST , { . i64 = - 1 } , INT_MIN , INT_MAX , FLAGS , " shaping_mode " } ,
{ " simple " , " simple shaping " , 0 , AV_OPT_TYPE_CONST , { . i64 = ASS_SHAPING_SIMPLE } , INT_MIN , INT_MAX , FLAGS , " shaping_mode " } ,
{ " complex " , " complex shaping " , 0 , AV_OPT_TYPE_CONST , { . i64 = ASS_SHAPING_COMPLEX } , INT_MIN , INT_MAX , FLAGS , " shaping_mode " } ,
2012-12-31 13:07:29 +03:00
{ NULL } ,
} ;
2012-11-29 05:28:37 +03:00
AVFILTER_DEFINE_CLASS ( ass ) ;
2013-04-12 12:13:33 +03:00
static av_cold int init_ass ( AVFilterContext * ctx )
2012-11-29 05:28:37 +03:00
{
AssContext * ass = ctx - > priv ;
2013-04-12 12:13:33 +03:00
int ret = init ( ctx ) ;
2012-11-29 05:28:37 +03:00
if ( ret < 0 )
return ret ;
2014-04-09 06:18:35 +03:00
/* Initialize fonts */
ass_set_fonts ( ass - > renderer , NULL , NULL , 1 , NULL , 1 ) ;
2012-11-29 05:28:37 +03:00
ass - > track = ass_read_file ( ass - > library , ass - > filename , NULL ) ;
if ( ! ass - > track ) {
av_log ( ctx , AV_LOG_ERROR ,
" Could not create a libass track when reading file '%s' \n " ,
ass - > filename ) ;
return AVERROR ( EINVAL ) ;
}
return 0 ;
}
2013-10-29 13:50:56 +03:00
AVFilter ff_vf_ass = {
2011-09-17 16:52:19 +03:00
. name = " ass " ,
2013-02-17 14:33:55 +03:00
. description = NULL_IF_CONFIG_SMALL ( " Render ASS subtitles onto input video using the libass library. " ) ,
2011-09-17 16:52:19 +03:00
. priv_size = sizeof ( AssContext ) ,
2012-11-29 05:28:37 +03:00
. init = init_ass ,
2011-09-17 16:52:19 +03:00
. uninit = uninit ,
. query_formats = query_formats ,
2012-11-28 22:01:59 +03:00
. inputs = ass_inputs ,
. outputs = ass_outputs ,
. priv_class = & ass_class ,
2011-09-17 16:52:19 +03:00
} ;
2012-11-29 05:28:37 +03:00
# endif
# if CONFIG_SUBTITLES_FILTER
2012-12-31 13:07:29 +03:00
static const AVOption subtitles_options [ ] = {
COMMON_OPTIONS
2014-05-01 13:51:17 +03:00
{ " charenc " , " set input character encoding " , OFFSET ( charenc ) , AV_OPT_TYPE_STRING , { . str = NULL } , CHAR_MIN , CHAR_MAX , FLAGS } ,
{ " stream_index " , " set stream index " , OFFSET ( stream_index ) , AV_OPT_TYPE_INT , { . i64 = - 1 } , - 1 , INT_MAX , FLAGS } ,
{ " si " , " set stream index " , OFFSET ( stream_index ) , AV_OPT_TYPE_INT , { . i64 = - 1 } , - 1 , INT_MAX , FLAGS } ,
2012-12-31 13:07:29 +03:00
{ NULL } ,
} ;
2014-08-29 01:32:32 +03:00
static const char * const font_mimetypes [ ] = {
2014-04-09 06:18:35 +03:00
" application/x-truetype-font " ,
" application/vnd.ms-opentype " ,
" application/x-font-ttf " ,
NULL
} ;
static int attachment_is_font ( AVStream * st )
{
const AVDictionaryEntry * tag = NULL ;
int n ;
tag = av_dict_get ( st - > metadata , " mimetype " , NULL , AV_DICT_MATCH_CASE ) ;
if ( tag ) {
for ( n = 0 ; font_mimetypes [ n ] ; n + + ) {
if ( av_strcasecmp ( font_mimetypes [ n ] , tag - > value ) = = 0 )
return 1 ;
}
}
return 0 ;
}
2012-11-29 05:28:37 +03:00
AVFILTER_DEFINE_CLASS ( subtitles ) ;
2013-04-12 12:13:33 +03:00
static av_cold int init_subtitles ( AVFilterContext * ctx )
2012-11-29 05:28:37 +03:00
{
2014-04-09 06:18:35 +03:00
int j , ret , sid ;
2014-05-01 13:51:17 +03:00
int k = 0 ;
2012-12-31 13:15:59 +03:00
AVDictionary * codec_opts = NULL ;
2012-11-29 05:28:37 +03:00
AVFormatContext * fmt = NULL ;
AVCodecContext * dec_ctx = NULL ;
AVCodec * dec = NULL ;
2013-02-20 21:47:32 +03:00
const AVCodecDescriptor * dec_desc ;
2012-11-29 05:28:37 +03:00
AVStream * st ;
AVPacket pkt ;
AssContext * ass = ctx - > priv ;
/* Init libass */
2013-04-12 12:13:33 +03:00
ret = init ( ctx ) ;
2012-11-29 05:28:37 +03:00
if ( ret < 0 )
return ret ;
ass - > track = ass_new_track ( ass - > library ) ;
if ( ! ass - > track ) {
av_log ( ctx , AV_LOG_ERROR , " Could not create a libass track \n " ) ;
return AVERROR ( EINVAL ) ;
}
/* Open subtitles file */
ret = avformat_open_input ( & fmt , ass - > filename , NULL , NULL ) ;
if ( ret < 0 ) {
av_log ( ctx , AV_LOG_ERROR , " Unable to open %s \n " , ass - > filename ) ;
goto end ;
}
ret = avformat_find_stream_info ( fmt , NULL ) ;
if ( ret < 0 )
goto end ;
/* Locate subtitles stream */
2014-05-01 13:51:17 +03:00
if ( ass - > stream_index < 0 )
ret = av_find_best_stream ( fmt , AVMEDIA_TYPE_SUBTITLE , - 1 , - 1 , NULL , 0 ) ;
else {
ret = - 1 ;
if ( ass - > stream_index < fmt - > nb_streams ) {
for ( j = 0 ; j < fmt - > nb_streams ; j + + ) {
if ( fmt - > streams [ j ] - > codec - > codec_type = = AVMEDIA_TYPE_SUBTITLE ) {
if ( ass - > stream_index = = k ) {
ret = j ;
break ;
}
k + + ;
}
}
}
}
2012-11-29 05:28:37 +03:00
if ( ret < 0 ) {
av_log ( ctx , AV_LOG_ERROR , " Unable to locate subtitle stream in %s \n " ,
ass - > filename ) ;
goto end ;
}
sid = ret ;
st = fmt - > streams [ sid ] ;
2014-04-09 06:18:35 +03:00
/* Load attached fonts */
for ( j = 0 ; j < fmt - > nb_streams ; j + + ) {
AVStream * st = fmt - > streams [ j ] ;
if ( st - > codec - > codec_type = = AVMEDIA_TYPE_ATTACHMENT & &
attachment_is_font ( st ) ) {
const AVDictionaryEntry * tag = NULL ;
tag = av_dict_get ( st - > metadata , " filename " , NULL ,
AV_DICT_MATCH_CASE ) ;
if ( tag ) {
av_log ( ctx , AV_LOG_DEBUG , " Loading attached font: %s \n " ,
tag - > value ) ;
ass_add_font ( ass - > library , tag - > value ,
st - > codec - > extradata ,
st - > codec - > extradata_size ) ;
} else {
av_log ( ctx , AV_LOG_WARNING ,
" Font attachment has no filename, ignored. \n " ) ;
}
}
}
/* Initialize fonts */
ass_set_fonts ( ass - > renderer , NULL , NULL , 1 , NULL , 1 ) ;
2012-11-29 05:28:37 +03:00
/* Open decoder */
dec_ctx = st - > codec ;
dec = avcodec_find_decoder ( dec_ctx - > codec_id ) ;
if ( ! dec ) {
av_log ( ctx , AV_LOG_ERROR , " Failed to find subtitle codec %s \n " ,
avcodec_get_name ( dec_ctx - > codec_id ) ) ;
return AVERROR ( EINVAL ) ;
}
2013-02-17 15:33:52 +03:00
dec_desc = avcodec_descriptor_get ( dec_ctx - > codec_id ) ;
2013-04-16 16:00:13 +03:00
if ( dec_desc & & ! ( dec_desc - > props & AV_CODEC_PROP_TEXT_SUB ) ) {
2013-02-17 15:33:52 +03:00
av_log ( ctx , AV_LOG_ERROR ,
" Only text based subtitles are currently supported \n " ) ;
return AVERROR_PATCHWELCOME ;
}
2012-12-31 13:15:59 +03:00
if ( ass - > charenc )
av_dict_set ( & codec_opts , " sub_charenc " , ass - > charenc , 0 ) ;
ret = avcodec_open2 ( dec_ctx , dec , & codec_opts ) ;
2012-11-29 05:28:37 +03:00
if ( ret < 0 )
goto end ;
/* Decode subtitles and push them into the renderer (libass) */
if ( dec_ctx - > subtitle_header )
ass_process_codec_private ( ass - > track ,
dec_ctx - > subtitle_header ,
dec_ctx - > subtitle_header_size ) ;
av_init_packet ( & pkt ) ;
pkt . data = NULL ;
pkt . size = 0 ;
while ( av_read_frame ( fmt , & pkt ) > = 0 ) {
int i , got_subtitle ;
2013-04-25 01:56:36 +03:00
AVSubtitle sub = { 0 } ;
2012-11-29 05:28:37 +03:00
if ( pkt . stream_index = = sid ) {
ret = avcodec_decode_subtitle2 ( dec_ctx , & sub , & got_subtitle , & pkt ) ;
2013-02-13 12:26:42 +03:00
if ( ret < 0 ) {
av_log ( ctx , AV_LOG_WARNING , " Error decoding: %s (ignored) \n " ,
av_err2str ( ret ) ) ;
} else if ( got_subtitle ) {
2013-02-14 18:35:49 +03:00
for ( i = 0 ; i < sub . num_rects ; i + + ) {
char * ass_line = sub . rects [ i ] - > ass ;
if ( ! ass_line )
break ;
ass_process_data ( ass - > track , ass_line , strlen ( ass_line ) ) ;
}
2013-02-13 12:26:42 +03:00
}
2012-11-29 05:28:37 +03:00
}
av_free_packet ( & pkt ) ;
avsubtitle_free ( & sub ) ;
}
end :
2012-12-31 13:15:59 +03:00
av_dict_free ( & codec_opts ) ;
2012-11-29 05:28:37 +03:00
if ( dec_ctx )
avcodec_close ( dec_ctx ) ;
2012-12-05 01:56:00 +03:00
if ( fmt )
avformat_close_input ( & fmt ) ;
2012-11-29 05:28:37 +03:00
return ret ;
}
2013-10-29 13:50:56 +03:00
AVFilter ff_vf_subtitles = {
2012-11-29 05:28:37 +03:00
. name = " subtitles " ,
2013-02-17 14:33:55 +03:00
. description = NULL_IF_CONFIG_SMALL ( " Render text subtitles onto input video using the libass library. " ) ,
2012-11-29 05:28:37 +03:00
. priv_size = sizeof ( AssContext ) ,
. init = init_subtitles ,
. uninit = uninit ,
. query_formats = query_formats ,
. inputs = ass_inputs ,
. outputs = ass_outputs ,
. priv_class = & subtitles_class ,
} ;
# endif