// Copyright (c) 2006-2007, Rodrigo Braz Monteiro, Evgeniy Stepanov // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of the Aegisub Group nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // Aegisub Project http://www.aegisub.org/ // // $Id$ /// @file subtitles_provider_libass.cpp /// @brief libass-based subtitle renderer /// @ingroup subtitle_rendering /// #include "config.h" #ifdef WITH_LIBASS #ifndef AGI_PRE #include #include #endif #ifdef __APPLE__ #include #include #endif #include #include "ass_file.h" #include "dialog_progress.h" #include "frame_main.h" #include "main.h" #include "standard_paths.h" #include "subtitles_provider_libass.h" #include "utils.h" #include "video_context.h" #include "video_frame.h" /// @brief Handle libass messages /// static void msg_callback(int level, const char *fmt, va_list args, void *) { if (level >= 7) return; char buf[1024]; #ifdef _WIN32 vsprintf_s(buf, sizeof(buf), fmt, args); #else vsnprintf(buf, sizeof(buf), fmt, args); #endif if (level < 2) // warning/error LOG_I("subtitle/provider/libass") << buf; else // verbose LOG_D("subtitle/provider/libass") << buf; } class FontConfigCacheThread : public wxThread { ASS_Library *ass_library; ASS_Renderer *ass_renderer; FontConfigCacheThread** thisPtr; ExitCode Entry() { const char *config_path = NULL; #ifdef __APPLE__ std::string conf_path = agi::util::OSX_GetBundleResourcesDirectory() + "/etc/fonts/fonts.conf"; config_path = conf_path.c_str(); #endif if (ass_library) ass_renderer = ass_renderer_init(ass_library); ass_set_fonts(ass_renderer, NULL, "Sans", 1, config_path, true); if (ass_library) ass_renderer_done(ass_renderer); *thisPtr = NULL; return EXIT_SUCCESS; } public: FontConfigCacheThread(ASS_Library *ass_library, FontConfigCacheThread **thisPtr) : ass_library(ass_library) , ass_renderer(NULL) , thisPtr(thisPtr) { *thisPtr = this; Create(); Run(); } FontConfigCacheThread(ASS_Renderer *ass_renderer, FontConfigCacheThread **thisPtr) : ass_library(NULL) , ass_renderer(ass_renderer) , thisPtr(thisPtr) { *thisPtr = this; Create(); Run(); } }; static void do_wait(agi::ProgressSink *ps, FontConfigCacheThread const * const * const cache_worker) { ps->SetIndeterminate(); while (*cache_worker && !ps->IsCancelled()) wxMilliSleep(100); } static void wait_for_cache_thread(FontConfigCacheThread const * const * const cache_worker) { if (!*cache_worker) return; DialogProgress progress(wxGetApp().frame, "Updating font index", "This may take several minutes"); progress.Run(bind(do_wait, std::tr1::placeholders::_1, cache_worker)); } /// @brief Constructor /// LibassSubtitlesProvider::LibassSubtitlesProvider(std::string) { wait_for_cache_thread(&cache_worker); // Initialize renderer ass_track = NULL; ass_renderer = ass_renderer_init(ass_library); if (!ass_renderer) throw "ass_renderer_init failed"; ass_set_font_scale(ass_renderer, 1.); new FontConfigCacheThread(ass_renderer, &cache_worker); wait_for_cache_thread(&cache_worker); } /// @brief Destructor /// LibassSubtitlesProvider::~LibassSubtitlesProvider() { if (ass_track) ass_free_track(ass_track); if (ass_renderer) ass_renderer_done(ass_renderer); } /// @brief Load subtitles /// @param subs /// void LibassSubtitlesProvider::LoadSubtitles(AssFile *subs) { // Prepare subtitles std::vector data; subs->SaveMemory(data); // Load file if (ass_track) ass_free_track(ass_track); ass_track = ass_read_memory(ass_library, &data[0], data.size(),(char *)"UTF-8"); if (!ass_track) throw "libass failed to load subtitles."; } /// DOCME #define _r(c) ((c)>>24) /// DOCME #define _g(c) (((c)>>16)&0xFF) /// DOCME #define _b(c) (((c)>>8)&0xFF) /// DOCME #define _a(c) ((c)&0xFF) /// @brief Draw subtitles /// @param frame /// @param time /// @return /// void LibassSubtitlesProvider::DrawSubtitles(AegiVideoFrame &frame,double time) { // Set size ass_set_frame_size(ass_renderer, frame.w, frame.h); // Get frame ASS_Image* img = ass_render_frame(ass_renderer, ass_track, int(time * 1000), NULL); // libass actually returns several alpha-masked monochrome images. // Here, we loop through their linked list, get the colour of the current, and blend into the frame. // This is repeated for all of them. while (img) { // Get colours unsigned int opacity = 255 - ((unsigned int)_a(img->color)); unsigned int r = (unsigned int)_r(img->color); unsigned int g = (unsigned int)_g(img->color); unsigned int b = (unsigned int) _b(img->color); // Prepare copy int src_stride = img->stride; int dst_stride = frame.pitch; const unsigned char *src = img->bitmap; unsigned char *dst = frame.data; if (frame.flipped) { dst += dst_stride * (frame.h - 1); dst_stride *= -1; } dst += (img->dst_y * dst_stride + img->dst_x * 4); // Copy image to destination frame for (int y = 0; y < img->h; y++) { unsigned char *dstp = dst; for (int x = 0; x < img->w; ++x) { unsigned int k = ((unsigned)src[x]) * opacity / 255; unsigned int ck = 255 - k; *dstp = (k * b + ck * *dstp) / 255; ++dstp; *dstp = (k * g + ck * *dstp) / 255; ++dstp; *dstp = (k * r + ck * *dstp) / 255; ++dstp; ++dstp; } dst += dst_stride; src += src_stride; } // Next image img = img->next; } } void LibassSubtitlesProvider::CacheFonts() { ass_library = ass_library_init(); ass_set_message_cb(ass_library, msg_callback, NULL); new FontConfigCacheThread(ass_library, &cache_worker); } /// DOCME ASS_Library* LibassSubtitlesProvider::ass_library; FontConfigCacheThread* LibassSubtitlesProvider::cache_worker = NULL; #endif // WITH_LIBASS