2011-09-17 15:52:19 +02:00
/*
* Copyright ( c ) 2011 Baptiste Coudurier
* Copyright ( c ) 2011 Stefano Sabatini
*
* 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>
# include "libavutil/avstring.h"
# include "libavutil/imgutils.h"
2012-03-19 19:28:56 +01:00
# include "libavutil/opt.h"
# include "libavutil/parseutils.h"
2011-09-17 15:52:19 +02:00
# include "drawutils.h"
# include "avfilter.h"
typedef struct {
2012-03-19 19:28:56 +01:00
const AVClass * class ;
2011-09-17 15:52:19 +02:00
ASS_Library * library ;
ASS_Renderer * renderer ;
ASS_Track * track ;
char * filename ;
uint8_t rgba_map [ 4 ] ;
int pix_step [ 4 ] ; ///< steps per pixel for each plane of the main output
2012-03-22 20:59:36 +01:00
char * original_size_str ;
int original_w , original_h ;
2012-03-28 15:51:13 +02:00
FFDrawContext draw ;
2011-09-17 15:52:19 +02:00
} AssContext ;
2012-03-19 19:28:56 +01:00
# define OFFSET(x) offsetof(AssContext, x)
static const AVOption ass_options [ ] = {
2012-03-22 20:59:36 +01:00
{ " original_size " , " set the size of the original video (used to scale fonts) " , OFFSET ( original_size_str ) , AV_OPT_TYPE_STRING , { . str = NULL } , CHAR_MIN , CHAR_MAX } ,
2012-03-19 19:28:56 +01:00
{ NULL } ,
} ;
static const char * ass_get_name ( void * ctx )
{
return " ass " ;
}
static const AVClass ass_class = {
" AssContext " ,
ass_get_name ,
ass_options
} ;
2011-09-17 15:52:19 +02:00
/* libass supports a log level ranging from 0 to 7 */
int ass_libav_log_level_map [ ] = {
AV_LOG_QUIET , /* 0 */
AV_LOG_PANIC , /* 1 */
AV_LOG_FATAL , /* 2 */
AV_LOG_ERROR , /* 3 */
AV_LOG_WARNING , /* 4 */
AV_LOG_INFO , /* 5 */
AV_LOG_VERBOSE , /* 6 */
AV_LOG_DEBUG , /* 7 */
} ;
static void ass_log ( int ass_level , const char * fmt , va_list args , void * ctx )
{
int level = ass_libav_log_level_map [ ass_level ] ;
av_vlog ( ctx , level , fmt , args ) ;
av_log ( ctx , level , " \n " ) ;
}
static av_cold int init ( AVFilterContext * ctx , const char * args , void * opaque )
{
AssContext * ass = ctx - > priv ;
2012-03-19 19:28:56 +01:00
int ret ;
ass - > class = & ass_class ;
av_opt_set_defaults ( ass ) ;
2011-09-17 15:52:19 +02:00
if ( args )
ass - > filename = av_get_token ( & args , " : " ) ;
if ( ! ass - > filename | | ! * ass - > filename ) {
av_log ( ctx , AV_LOG_ERROR , " No filename provided! \n " ) ;
return AVERROR ( EINVAL ) ;
}
2012-03-19 19:28:56 +01:00
if ( * args + + = = ' : ' & & ( ret = av_set_options_string ( ass , args , " = " , " : " ) ) < 0 ) {
av_log ( ctx , AV_LOG_ERROR , " Error parsing options string: '%s' \n " , args ) ;
return ret ;
}
2012-03-22 20:59:36 +01:00
if ( ass - > original_size_str & &
av_parse_video_size ( & ass - > original_w , & ass - > original_h ,
ass - > original_size_str ) < 0 ) {
2012-03-19 19:28:56 +01:00
av_log ( ctx , AV_LOG_ERROR ,
2012-03-22 20:59:36 +01:00
" Invalid original size '%s'. \n " , ass - > original_size_str ) ;
2012-03-19 19:28:56 +01:00
return AVERROR ( EINVAL ) ;
}
2011-09-17 15:52:19 +02:00
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 ) ;
}
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 ) ;
}
ass_set_fonts ( ass - > renderer , NULL , NULL , 1 , NULL , 1 ) ;
return 0 ;
}
static av_cold void uninit ( AVFilterContext * ctx )
{
AssContext * ass = ctx - > priv ;
av_freep ( & ass - > filename ) ;
2012-03-22 20:59:36 +01:00
av_freep ( & ass - > original_size_str ) ;
2011-09-17 15:52:19 +02:00
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-03-28 15:51:13 +02:00
avfilter_set_common_pixel_formats ( ctx , ff_draw_supported_pixel_formats ( 0 ) ) ;
2011-09-17 15:52:19 +02:00
return 0 ;
}
static int config_input ( AVFilterLink * inlink )
{
AssContext * ass = inlink - > dst - > priv ;
2012-03-28 15:51:13 +02:00
ff_draw_init ( & ass - > draw , inlink - > format , 0 ) ;
2011-09-17 15:52:19 +02:00
ass_set_frame_size ( ass - > renderer , inlink - > w , inlink - > h ) ;
2012-03-22 20:59:36 +01: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 ) ;
2011-09-17 15:52:19 +02:00
return 0 ;
}
static void null_draw_slice ( AVFilterLink * link , int y , int h , int slice_dir ) { }
/* 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)
2012-03-28 15:51:13 +02:00
static void overlay_ass_image ( AssContext * ass , AVFilterBufferRef * picref ,
2011-09-17 15:52:19 +02: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 15:51:13 +02:00
FFDrawColor color ;
ff_draw_color ( & ass - > draw , & color , rgba_color ) ;
ff_blend_mask ( & ass - > draw , & color ,
picref - > data , picref - > linesize ,
picref - > video - > w , picref - > video - > h ,
image - > bitmap , image - > stride , image - > w , image - > h ,
3 , 0 , image - > dst_x , image - > dst_y ) ;
2011-09-17 15:52:19 +02:00
}
}
static void end_frame ( AVFilterLink * inlink )
{
AVFilterContext * ctx = inlink - > dst ;
AVFilterLink * outlink = ctx - > outputs [ 0 ] ;
AssContext * ass = ctx - > priv ;
AVFilterBufferRef * picref = inlink - > cur_buf ;
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 15:51:13 +02:00
overlay_ass_image ( ass , picref , image ) ;
2011-09-17 15:52:19 +02:00
avfilter_draw_slice ( outlink , 0 , picref - > video - > h , 1 ) ;
avfilter_end_frame ( outlink ) ;
}
AVFilter avfilter_vf_ass = {
. name = " ass " ,
. description = NULL_IF_CONFIG_SMALL ( " Render subtitles onto input video using the libass library. " ) ,
. priv_size = sizeof ( AssContext ) ,
. init = init ,
. uninit = uninit ,
. query_formats = query_formats ,
. inputs = ( const AVFilterPad [ ] ) {
{ . name = " default " ,
. type = AVMEDIA_TYPE_VIDEO ,
. get_video_buffer = avfilter_null_get_video_buffer ,
. start_frame = avfilter_null_start_frame ,
. draw_slice = null_draw_slice ,
. end_frame = end_frame ,
. config_props = config_input ,
. min_perms = AV_PERM_WRITE | AV_PERM_READ ,
. rej_perms = AV_PERM_PRESERVE } ,
{ . name = NULL }
} ,
. outputs = ( const AVFilterPad [ ] ) {
{ . name = " default " ,
. type = AVMEDIA_TYPE_VIDEO , } ,
{ . name = NULL }
} ,
} ;