diff --git a/configure.ac b/configure.ac index 368cd03..3fdfbfa 100644 --- a/configure.ac +++ b/configure.ac @@ -143,12 +143,30 @@ fi AC_DEFINE_UNQUOTED([HAVE_LIBEQUALIZER], [$HAVE_LIBEQUALIZER], [Have Equalizer?]) AM_CONDITIONAL([HAVE_LIBEQUALIZER], [test "$HAVE_LIBEQUALIZER" = "1"]) +dnl FTGL +AC_ARG_WITH([ftgl], + [AS_HELP_STRING([--with-ftgl], [Enable subtitles via FTGL (disabled by default)])], + [if test "$withval" = "yes"; then ftgl="yes"; else ftgl="no "; fi], [ftgl="no"]) +if test "$ftgl" = "yes"; then + PKG_CHECK_MODULES([libftgl], [ftgl >= 0.0], [HAVE_LIBFTGL=1], [HAVE_LIBFTGL=0]) + if test "$HAVE_LIBFTGL" != "1"; then + AC_MSG_WARN([library libftgl not found:]) + AC_MSG_WARN([$libftgl_PKG_ERRORS]) + AC_MSG_WARN([libftgl is provided by FTGL]) + fi +else + HAVE_LIBFTGL=0 +fi +AC_DEFINE_UNQUOTED([HAVE_LIBFTGL], [$HAVE_LIBFTGL], [Have FTGL?]) +AM_CONDITIONAL([HAVE_LIBFTGL], [test "$HAVE_LIBFTGL" = "1"]) + dnl Check if all libraries were found if test "$HAVE_LIBAVFORMAT" != "1" \ -o "$HAVE_LIBSWSCALE" != "1" \ -o "$HAVE_LIBOPENAL" != "1" \ -o "$HAVE_LIBQTOPENGL" != "1" \ -o \( "$equalizer" = "yes" -a "$HAVE_LIBEQUALIZER" != "1" \) \ + -o \( "$ftgl" = "yes" -a "$HAVE_LIBFTGL" != "1" \) \ -o \( "$HAVE_LIBEQUALIZER" != "1" -a "$HAVE_LIBGLEW" != "1" \) ; then AC_MSG_ERROR([One or more libraries were not found. See messages above.]) fi diff --git a/src/Makefile.am b/src/Makefile.am index 7cf7ec9..fe2f0a3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -82,6 +82,11 @@ AM_CPPFLAGS += $(libglew_CFLAGS) bino_LDADD += $(libglew_LIBS) endif +if HAVE_LIBFTGL +AM_CPPFLAGS += $(libftgl_CFLAGS) +bino_LDADD += $(libftgl_LIBS) +endif + if W32 bino_SOURCES += logo/bino_logo.ico .ico.o: diff --git a/src/controller.h b/src/controller.h index 40c93a6..20c48a7 100644 --- a/src/controller.h +++ b/src/controller.h @@ -53,6 +53,8 @@ public: set_video_stream, // int cycle_audio_stream, // no parameters set_audio_stream, // int + cycle_subtitles_stream, // no parameters + set_subtitles_stream, // int set_stereo_layout, // video_frame::stereo_layout, bool set_stereo_mode, // parameters::stereo_mode, bool toggle_stereo_mode_swap, // no parameters @@ -72,7 +74,10 @@ public: set_parallax, // float (absolute value) set_crosstalk, // 3 floats (absolute values) adjust_ghostbust, // float (relative adjustment) - set_ghostbust // float (absolute value) + set_ghostbust, // float (absolute value) + set_subtitles_font, // filename, string + set_subtitles_size, // int + set_subtitles_color // RGB color, int }; type type; @@ -116,6 +121,7 @@ public: pause, // bool video_stream, // int audio_stream, // int + subtitles_stream, // int stereo_layout, // video_frame::stereo_layout, bool stereo_mode, // parameters::stereo_mode, bool stereo_mode_swap, // bool @@ -128,7 +134,11 @@ public: pos, // float parallax, // float crosstalk, // 3 floats - ghostbust // float + ghostbust, // float + subtitles_font, // string + subtitles_size, // int + subtitles_color, // int + }; type type; diff --git a/src/media_data.cpp b/src/media_data.cpp index 2168df2..ef71b06 100644 --- a/src/media_data.cpp +++ b/src/media_data.cpp @@ -44,7 +44,8 @@ video_frame::video_frame() : chroma_location(center), stereo_layout(mono), stereo_layout_swap(false), - presentation_time(std::numeric_limits::min()) + presentation_time(std::numeric_limits::min()), + subtitle(NULL) { for (int i = 0; i < 2; i++) { @@ -426,6 +427,104 @@ int audio_blob::sample_bits() const return bits; } +std::string subtitles_list::format_info() const +{ + return format_name(); +} + +std::string subtitles_list::format_name() const +{ + const char *format_name; + switch (format) + { + case text: + format_name = "text"; + break; + case image: + format_name = "image"; + break; + case ssa: + format_name = "srt"; + break; + } + return str::asprintf("%s, %s", lang.empty() ? "Unknown language" : lang.c_str(), format_name); +} + +subtitles_list::subtitles_list():format(text),current(0) +{ +} + +subtitles_list::subtitle* subtitles_list::get_current_subtitle(int64_t time) +{ + if(current >= data.size()) + return NULL; + + switch (format) + { + case text: + { + size_t next = (current + 1) % 2; + if(data[next].start_time && time > data[next].start_time && time < data[next].end_time) + { + data[current].start_time = data[current].end_time = 0; + current=next; + return &data[current]; + } + if(time > data[current].start_time && time < data[current].end_time) + return &data[current]; + return NULL; + } + break; + + case image: + return NULL; // TODO: + break; + + case ssa: + { + if(time < data[current].start_time) + return NULL; + + if(current < data.size()-2 && time > data[current+1].start_time) + if(find_next_subtitle(time) == data.size()) + return NULL; // End of subtitles + + if(time > data[current].start_time && time < data[current].end_time) + return &data[current]; + + if(find_next_subtitle(time) == data.size()) + return NULL; // End of subtitles + + if(time > data[current].start_time) + return &data[current]; // Found the right one + + return NULL; + } + break; + } +} + +size_t subtitles_list::find_next_subtitle(int64_t time) +{ + for(; current < data.size(); current++) + if(time < data[current].end_time) + return current; + return current+1; +} + + +const char* subtitles_list::extract_text_from_ssa(const char* text) +{ + // Very naive - return text after third comma + //TODO: Add some advanced logic + const char * text_pos = text; + for(int i = 0; i < 3; i++) + if((text_pos = strchr(text_pos, ',')) != NULL) + text_pos++; + + return text_pos ? text_pos : ""; +} + parameters::parameters() : stereo_mode(stereo), stereo_mode_swap(false), @@ -437,7 +536,10 @@ parameters::parameters() : contrast(std::numeric_limits::quiet_NaN()), brightness(std::numeric_limits::quiet_NaN()), hue(std::numeric_limits::quiet_NaN()), - saturation(std::numeric_limits::quiet_NaN()) + saturation(std::numeric_limits::quiet_NaN()), + subtitles_color(0x00FFFFFF), + subtitles_size(12), + subtitles_font("") { } @@ -479,6 +581,18 @@ void parameters::set_defaults() { saturation = 0.0f; } + if (subtitles_color < 0 || subtitles_color > 0x00FFFFFF) + { + subtitles_color = 0x00FFFFFF; + } + if (subtitles_size < 1 || subtitles_size > 150) + { + subtitles_size = 12; + } + if (subtitles_font.empty()) + { + subtitles_font = "Arial.ttf"; + } } std::string parameters::stereo_mode_to_string(stereo_mode_t stereo_mode, bool stereo_mode_swap) @@ -696,6 +810,9 @@ void parameters::save(std::ostream &os) const s11n::save(os, brightness); s11n::save(os, hue); s11n::save(os, saturation); + s11n::save(os, subtitles_color); + s11n::save(os, subtitles_font); + s11n::save(os, subtitles_size); } void parameters::load(std::istream &is) @@ -713,4 +830,7 @@ void parameters::load(std::istream &is) s11n::load(is, brightness); s11n::load(is, hue); s11n::load(is, saturation); + s11n::load(is, subtitles_color); + s11n::load(is, subtitles_font); + s11n::load(is, subtitles_size); } diff --git a/src/media_data.h b/src/media_data.h index ca1bd45..f559d87 100644 --- a/src/media_data.h +++ b/src/media_data.h @@ -27,6 +27,46 @@ #include "s11n.h" +class subtitles_list +{ +public: + // Sample format + typedef enum + { + text, + ssa, // subtitles stored as vector of strings + image, // subtitles stored as stream of images + } format_t; + + struct subtitle + { + std::string text; // subtitle text + int64_t start_time; // Presentation timestamp + int64_t end_time; + + subtitle(const char * t = ""):text(t),start_time(0),end_time(0){}; + }; + + format_t format; // Subtitles format + std::string lang; // Subtitles language + + typedef std::vector subtitles; + subtitles data; + size_t current; + + // Return a string describing the format + std::string format_info() const; // Human readable information + std::string format_name() const; // Short code + + // Constructor + subtitles_list(); + + // Returns pointer to actual subtitle or NULL if none should be diplayed + subtitle * get_current_subtitle(int64_t time); + static const char* extract_text_from_ssa(const char* text); + size_t find_next_subtitle(int64_t time); +}; + class video_frame { public: @@ -90,6 +130,7 @@ public: // so it does not free them on destruction. void *data[2][3]; // Data pointer for 1-3 planes in 1-2 views. NULL if unused. size_t line_size[2][3]; // Line size for 1-3 planes in 1-2 views. 0 if unused. + subtitles_list::subtitle *subtitle; // Associated subtitles with frame int64_t presentation_time; // Presentation timestamp @@ -200,6 +241,10 @@ public: float brightness; // Brightness adjustment, -1 .. +1 float hue; // Hue adjustment, -1 .. +1 float saturation; // Saturation adjustment, -1 .. +1 + + int subtitles_color; // RGB color of subtitles + std::string subtitles_font; // Filepath of font for rendering + int subtitles_size; // Size of subtitles // Constructor parameters(); diff --git a/src/media_input.cpp b/src/media_input.cpp index 7c7f364..37d08ea 100644 --- a/src/media_input.cpp +++ b/src/media_input.cpp @@ -32,8 +32,8 @@ media_input::media_input() : - _active_video_stream(-1), _active_audio_stream(-1), - _have_active_video_read(false), _have_active_audio_read(false), _last_audio_data_size(0), + _active_video_stream(-1), _active_audio_stream(-1), _active_subtitles_stream(-1), + _have_active_video_read(false), _have_active_audio_read(false), _have_active_subtitles_read(false), _last_audio_data_size(0), _initial_skip(0), _duration(-1) { } @@ -76,6 +76,23 @@ void media_input::get_audio_stream(int stream, int &media_object, int &media_obj } } +void media_input::get_subtitles_stream(int stream, int &media_object, int &media_object_subtitles_stream) const +{ + assert(stream < subtitles_streams()); + + for (size_t i = 0; i < _media_objects.size(); i++) + { + if (_media_objects[i].subtitles_streams() < stream + 1) + { + stream -= _media_objects[i].subtitles_streams(); + continue; + } + media_object = i; + media_object_subtitles_stream = stream; + break; + } +} + // Get the basename of an URL (just the file name, without leading paths) static std::string basename(const std::string &url) { @@ -147,6 +164,16 @@ void media_input::open(const std::vector &urls) + _media_objects[i].audio_blob_template(j).format_info()); } } + for (size_t i = 0; i < _media_objects.size(); i++) + { + std::string pfx = (_media_objects.size() == 1 ? "" : str::from(i + 1) + " - "); + for (int j = 0; j < _media_objects[i].subtitles_streams(); j++) + { + std::string pfx2 = (_media_objects[i].subtitles_streams() == 1 ? "" : str::from(j + 1) + " - "); + _subtitles_stream_names.push_back(pfx + pfx2 + + _media_objects[i].subtitles_list_template(j).format_info()); + } + } // Set duration information _duration = std::numeric_limits::max(); @@ -226,6 +253,16 @@ void media_input::open(const std::vector &urls) _audio_blob = _media_objects[o].audio_blob_template(s); select_audio_stream(_active_audio_stream); } + + // Set active subtitles stream + _active_subtitles_stream = (subtitles_streams() > 0 ? 0 : -1); + if (_active_subtitles_stream >= 0) + { + int o, s; + get_subtitles_stream(_active_subtitles_stream, o, s); + _subtitles_list = &_media_objects[o].subtitles_list_template(s); + select_subtitles_stream(_active_subtitles_stream); + } // Print summary msg::inf("Input:"); @@ -251,6 +288,17 @@ void media_input::open(const std::vector &urls) { msg::inf(" No audio."); } + for (int i = 0; i < subtitles_streams(); i++) + { + int o, s; + get_subtitles_stream(i, o, s); + msg::inf(" Subtitles %s: %s", subtitles_stream_name(i).c_str(), + _media_objects[o].subtitles_list_template(s).format_name().c_str()); + } + if (subtitles_streams() == 0) + { + msg::inf(" No subtitles."); + } msg::inf(" Duration: %g seconds", duration() / 1e6f); if (video_streams() > 0) { @@ -328,6 +376,12 @@ const audio_blob &media_input::audio_blob_template() const return _audio_blob; } +const subtitles_list &media_input::subtitles_blob_template() const +{ + assert(_active_subtitles_stream >= 0); + return *_subtitles_list; +} + bool media_input::stereo_layout_is_supported(video_frame::stereo_layout_t layout, bool) const { if (video_streams() < 1) @@ -445,6 +499,32 @@ void media_input::select_audio_stream(int audio_stream) } } +void media_input::select_subtitles_stream(int subtitles_stream) +{ + if (_have_active_video_read) + { + (void)finish_video_frame_read(); + } + if (_have_active_audio_read) + { + (void)finish_audio_blob_read(); + } + assert(subtitles_stream >= 0); + assert(subtitles_stream < subtitles_streams()); + _active_subtitles_stream = subtitles_stream; + int o, s; + get_subtitles_stream(_active_subtitles_stream, o, s); + for (size_t i = 0; i < _media_objects.size(); i++) + { + for (int j = 0; j < _media_objects[i].subtitles_streams(); j++) + { + _media_objects[i].subtitles_stream_set_active(j, (i == static_cast(o) && j == s)); + } + } + + _subtitles_list = _media_objects[o].finish_subtitles_list_read(s); +} + void media_input::start_video_frame_read() { assert(_active_video_stream >= 0); @@ -513,6 +593,13 @@ video_frame media_input::finish_video_frame_read() frame.presentation_time = f.presentation_time; } } + + // Assign subtitle + if(_active_subtitles_stream >= 0) + { + frame.subtitle = _subtitles_list->get_current_subtitle(frame.presentation_time); + } + _have_active_video_read = false; return frame; } diff --git a/src/media_input.h b/src/media_input.h index 1857088..c2ea06a 100644 --- a/src/media_input.h +++ b/src/media_input.h @@ -37,12 +37,15 @@ private: std::vector _video_stream_names; // Descriptions of available video streams std::vector _audio_stream_names; // Descriptions of available audio streams + std::vector _subtitles_stream_names; // Descriptions of available subtitles streams bool _supports_stereo_layout_separate; // Does this input support the stereo layout 'separate_streams'? int _active_video_stream; // The video stream that is currently active. int _active_audio_stream; // The audio stream that is currently active. + int _active_subtitles_stream; // The audio stream that is currently active. bool _have_active_video_read; // Whether a video frame read was started. bool _have_active_audio_read; // Whether a audio blob read was started. + bool _have_active_subtitles_read; // Whether a audio blob read was started. size_t _last_audio_data_size; // Size of last audio blob read int64_t _initial_skip; // Initial portion of input to skip, in microseconds. @@ -50,10 +53,12 @@ private: video_frame _video_frame; // Video frame template for currently active video stream. audio_blob _audio_blob; // Audio blob template for currently active audio stream. + subtitles_list* _subtitles_list; // Subtitles list template for currently active subtitles stream. // Find the media object and its stream index for a given video or audio stream number. void get_video_stream(int stream, int &media_object, int &media_object_video_stream) const; void get_audio_stream(int stream, int &media_object, int &media_object_audio_stream) const; + void get_subtitles_stream(int stream, int &media_object, int &media_object_subtitles_stream) const; public: @@ -88,6 +93,12 @@ public: { return _audio_stream_names.size(); } + + // Number of subtitles streams in this input. + int subtitles_streams() const + { + return _subtitles_stream_names.size(); + } // Name of the given video stream. const std::string &video_stream_name(int video_stream) const @@ -100,6 +111,12 @@ public: { return _audio_stream_names[audio_stream]; } + + // Name of the given subtitles stream. + const std::string &subtitles_stream_name(int subtitles_stream) const + { + return _subtitles_stream_names[subtitles_stream]; + } // Initial portion of the input to skip. int64_t initial_skip() const @@ -125,6 +142,10 @@ public: // Information about the active audio stream, in the form of an audio blob // that contains all properties but no actual data. const audio_blob &audio_blob_template() const; + + // Information about the active subtitles stream, in the form of an subtitles blob + // that contains all properties but no actual data. + const subtitles_list &subtitles_blob_template() const; /* * Access video and audio data @@ -141,6 +162,11 @@ public: return _active_audio_stream; } void select_audio_stream(int audio_stream); + int selected_subtitles_stream() const + { + return _active_subtitles_stream; + } + void select_subtitles_stream(int subtitles_stream); /* Check whether a stereo layout is supported by this input. */ bool stereo_layout_is_supported(video_frame::stereo_layout_t layout, bool swap) const; diff --git a/src/media_object.cpp b/src/media_object.cpp index 1c45417..da1a949 100644 --- a/src/media_object.cpp +++ b/src/media_object.cpp @@ -153,6 +153,16 @@ struct ffmpeg_stuff std::vector audio_blobs; std::vector > audio_buffers; std::vector audio_last_timestamps; + + std::vector subtitles_streams; + std::vector subtitles_codec_ctxs; + std::vector subtitles_list_templates; + std::vector subtitles_codecs; + std::vector > subtitles_packet_queues; + std::vector subtitles_packet_queue_mutexes; + //std::vector subtitles_decode_threads; + std::vector subtitles_blobs; + std::vector subtitles_last_timestamps; }; // Use one decoding thread per processor for video decoding. @@ -554,6 +564,40 @@ void media_object::set_audio_blob_template(int index) } } +void media_object::set_subtitles_list_template(int index) +{ + AVStream *subtitles_stream = _ffmpeg->format_ctx->streams[_ffmpeg->subtitles_streams[index]]; + AVCodecContext *subtitles_codec_ctx = _ffmpeg->subtitles_codec_ctxs[index]; + subtitles_list &subtitles_blob_template = _ffmpeg->subtitles_list_templates[index]; + + subtitles_blob_template.lang = subtitles_stream->language; + + subtitles_list::format_t format; + switch(subtitles_stream->codec->codec_id) + { + case CODEC_ID_DVB_SUBTITLE: + format = subtitles_list::image; + break; + case CODEC_ID_SSA: + format = subtitles_list::ssa; + break; + case CODEC_ID_TEXT: + format = subtitles_list::text; + break; + case CODEC_ID_SRT: + format = subtitles_list::ssa; + break; + default: + format = subtitles_list::text; + } + subtitles_blob_template.format = format; + + if(subtitles_blob_template.format == subtitles_list::text) + subtitles_blob_template.data.resize(2); // Alloc two frames, one for current, second for next + + subtitles_blob_template.current = 0; +} + void media_object::open(const std::string &url) { assert(!_ffmpeg); @@ -685,6 +729,31 @@ void media_object::open(const std::string &url) _ffmpeg->audio_buffers.push_back(std::vector()); _ffmpeg->audio_last_timestamps.push_back(std::numeric_limits::min()); } + else if (_ffmpeg->format_ctx->streams[i]->codec->codec_type == CODEC_TYPE_SUBTITLE) + { + _ffmpeg->subtitles_streams.push_back(i); + int j = _ffmpeg->subtitles_streams.size() - 1; + msg::dbg(_url + " stream " + str::from(i) + " is subtitles stream " + str::from(j) + "."); + _ffmpeg->subtitles_codec_ctxs.push_back(_ffmpeg->format_ctx->streams[i]->codec); + _ffmpeg->subtitles_codecs.push_back(avcodec_find_decoder(_ffmpeg->subtitles_codec_ctxs[j]->codec_id)); + if(_ffmpeg->subtitles_codec_ctxs[j]->codec_id != CODEC_ID_TEXT) + { + if (!_ffmpeg->subtitles_codecs[j]) + { + throw exc(_url + " stream " + str::from(i) + ": Unsupported subtitles codec."); + } + if ((e = avcodec_open(_ffmpeg->subtitles_codec_ctxs[j], _ffmpeg->subtitles_codecs[j])) < 0) + { + _ffmpeg->subtitles_codecs[j] = NULL; + throw exc(_url + " stream " + str::from(i) + ": Cannot open subtitles codec: " + my_av_strerror(e)); + } + } + _ffmpeg->subtitles_list_templates.push_back(subtitles_list()); + set_subtitles_list_template(j); + + _ffmpeg->subtitles_blobs.push_back(blob()); + _ffmpeg->subtitles_last_timestamps.push_back(std::numeric_limits::min()); + } else { msg::dbg(_url + " stream " + str::from(i) + " contains neither video nor audio."); @@ -692,8 +761,10 @@ void media_object::open(const std::string &url) } _ffmpeg->video_packet_queues.resize(video_streams()); _ffmpeg->audio_packet_queues.resize(audio_streams()); + _ffmpeg->subtitles_packet_queues.resize(audio_streams()); _ffmpeg->video_packet_queue_mutexes.resize(video_streams()); _ffmpeg->audio_packet_queue_mutexes.resize(audio_streams()); + _ffmpeg->subtitles_packet_queue_mutexes.resize(audio_streams()); msg::inf(_url + ":"); for (int i = 0; i < video_streams(); i++) @@ -712,7 +783,11 @@ void media_object::open(const std::string &url) audio_blob_template(i).format_name().c_str(), audio_duration(i) / 1e6f); } - if (video_streams() == 0 && audio_streams() == 0) + for (int i = 0; i < subtitles_streams(); i++) + { + msg::inf(" Subtitles stream %d: %s / %s", i, subtitles_list_template(i).format_info().c_str(), subtitles_list_template(i).format_name().c_str()); + } + if (video_streams() == 0 && audio_streams() == 0 && subtitles_streams() == 0) { msg::inf(" No usable streams."); } @@ -763,6 +838,11 @@ int media_object::video_streams() const return _ffmpeg->video_streams.size(); } +int media_object::subtitles_streams() const +{ + return _ffmpeg->subtitles_streams.size(); +} + void media_object::video_stream_set_active(int index, bool active) { assert(index >= 0); @@ -816,6 +896,19 @@ void media_object::audio_stream_set_active(int index, bool active) _ffmpeg->reader->start(); } +void media_object::subtitles_stream_set_active(int index, bool active) +{ + assert(index >= 0); + assert(index < subtitles_streams()); + // Stop reading packets + _ffmpeg->reader->finish(); + // Set status + _ffmpeg->format_ctx->streams[_ffmpeg->subtitles_streams.at(index)]->discard = + (active ? AVDISCARD_DEFAULT : AVDISCARD_ALL); + // Restart reader + _ffmpeg->reader->start(); +} + const video_frame &media_object::video_frame_template(int video_stream) const { assert(video_stream >= 0); @@ -882,6 +975,13 @@ int64_t media_object::audio_duration(int index) const } } +subtitles_list& media_object::subtitles_list_template(int subtitles_stream) const +{ + assert(subtitles_stream >= 0); + assert(subtitles_stream < subtitles_streams()); + return _ffmpeg->subtitles_list_templates.at(subtitles_stream); +} + read_thread::read_thread(const std::string &url, struct ffmpeg_stuff *ffmpeg) : _url(url), _ffmpeg(ffmpeg), _eof(false) { @@ -913,6 +1013,14 @@ void read_thread::run() _ffmpeg->audio_packet_queue_mutexes[i].unlock(); } } + for (size_t i = 0; !need_another_packet && i < _ffmpeg->subtitles_streams.size(); i++) + { + if (_ffmpeg->format_ctx->streams[_ffmpeg->subtitles_streams[i]]->discard == AVDISCARD_DEFAULT) + { + if(_ffmpeg->video_streams.size() + _ffmpeg->audio_streams.size() == 0) // Read whole file if using external file + need_another_packet = true; + } + } if (!need_another_packet) { msg::dbg(_url + ": No need to read more packets."); @@ -989,6 +1097,39 @@ void read_thread::run() _ffmpeg->audio_packet_queue_mutexes[i].unlock(); } } + for (size_t i = 0; i < _ffmpeg->subtitles_streams.size() && !packet_queued; i++) + { + if (packet.stream_index == _ffmpeg->subtitles_streams[i]) + { + subtitles_list::subtitle * storage = NULL; + if(_ffmpeg->subtitles_list_templates[i].format == subtitles_list::ssa) + { + AVSubtitle subtitle; + int ptr; + avcodec_decode_subtitle2(_ffmpeg->subtitles_codec_ctxs[i], &subtitle, &ptr, &packet); + _ffmpeg->subtitles_list_templates[i].data.push_back(subtitles_list::extract_text_from_ssa(subtitle.rects[0]->ass)); + storage = &_ffmpeg->subtitles_list_templates[i].data.back(); + for(unsigned int j = 1; j < subtitle.num_rects; j++) + storage->text += std::string("\n") + subtitles_list::extract_text_from_ssa(subtitle.rects[j]->ass); + } + else + { + storage = &_ffmpeg->subtitles_list_templates[i].data[(_ffmpeg->subtitles_list_templates[i].current+1) % 2]; + storage->text = (char*)packet.data; + } + + storage->start_time = packet.dts * 1000000 + * _ffmpeg->format_ctx->streams[packet.stream_index]->time_base.num + / _ffmpeg->format_ctx->streams[packet.stream_index]->time_base.den; + if(!packet.duration) + packet.duration = 5000; + storage->end_time = (packet.dts + packet.duration) * 1000000 + * _ffmpeg->format_ctx->streams[packet.stream_index]->time_base.num + / _ffmpeg->format_ctx->streams[packet.stream_index]->time_base.den; + + av_free_packet(&packet); + } + } if (!packet_queued) { av_free_packet(&packet); @@ -1002,7 +1143,7 @@ void read_thread::reset() _eof = false; } -video_decode_thread::video_decode_thread(const std::string &url, struct ffmpeg_stuff *ffmpeg, int video_stream) : +video_decode_thread::video_decode_thread(const std::string& url, ffmpeg_stuff* ffmpeg, int video_stream) : _url(url), _ffmpeg(ffmpeg), _video_stream(video_stream), _frame() { } @@ -1237,6 +1378,18 @@ audio_blob media_object::finish_audio_blob_read(int audio_stream) return _ffmpeg->audio_decode_threads[audio_stream].blob(); } +void media_object::start_subtitles_list_read(int subtitles_stream, size_t size) +{ + // Do nothing yet +} + +subtitles_list* media_object::finish_subtitles_list_read(int subtitles_stream) +{ + assert(subtitles_stream >= 0); + assert(subtitles_stream < subtitles_streams()); + return &_ffmpeg->subtitles_list_templates[subtitles_stream]; +} + int64_t media_object::tell() { return _ffmpeg->pos; @@ -1294,6 +1447,10 @@ void media_object::seek(int64_t dest_pos) { _ffmpeg->audio_last_timestamps[i] = std::numeric_limits::min(); } + for (size_t i = 0; i < _ffmpeg->subtitles_streams.size(); i++) + { + _ffmpeg->subtitles_list_templates[i].current = 0; + } _ffmpeg->pos = std::numeric_limits::min(); // Restart packet reading _ffmpeg->reader->reset(); diff --git a/src/media_object.h b/src/media_object.h index 0c72343..91f79de 100644 --- a/src/media_object.h +++ b/src/media_object.h @@ -39,6 +39,7 @@ private: // from the given streams void set_video_frame_template(int video_stream); void set_audio_blob_template(int audio_stream); + void set_subtitles_list_template(int subtitles_stream); // The threaded implementation can access private members friend class read_thread; @@ -68,10 +69,12 @@ public: /* Get the number of video and audio streams in the file. */ int video_streams() const; int audio_streams() const; + int subtitles_streams() const; /* Activate a video or audio stream for usage. Inactive streams will not be accessible. */ void video_stream_set_active(int video_stream, bool active); void audio_stream_set_active(int audio_stream, bool active); + void subtitles_stream_set_active(int subtitles_stream, bool active); /* Get information about video streams. */ // Return a video frame with all properties filled in (but without any data). @@ -90,6 +93,11 @@ public: const audio_blob &audio_blob_template(int audio_stream) const; // Audio stream duration in microseconds. int64_t audio_duration(int video_stream) const; + + /* Get information about subtitles streams. */ + // Return an subtitles blob with all properties filled in (but without any data). + // Note that this is only a hint; the properties of actual subtitles blobs may differ! + subtitles_list &subtitles_list_template(int subtitles_stream) const; /* * Access video and audio data @@ -106,6 +114,12 @@ public: /* Wait for the audio data reading to finish, and return the blob. * An invalid blob means that EOF was reached. */ audio_blob finish_audio_blob_read(int audio_stream); + + /* Start to read the given amount of subtitles data asynchronously (in a separate thread). */ + void start_subtitles_list_read(int subtitles_stream, size_t size); + /* Wait for the subtitles data reading to finish, and return the subtitles_list. + * An invalid subtitles_list means that EOF was reached. */ + subtitles_list* finish_subtitles_list_read(int subtitles_stream); /* Return the last position in microseconds, of the last packet that was read in any * stream. If the position is unkown, the minimum possible value is returned. */ diff --git a/src/player.cpp b/src/player.cpp index 33d042d..3cdd6fc 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -42,6 +42,7 @@ player_init_data::player_init_data() : urls(), video_stream(0), audio_stream(0), + subtitles_stream(0), benchmark(false), fullscreen(false), center(false), @@ -65,6 +66,7 @@ void player_init_data::save(std::ostream &os) const s11n::save(os, urls); s11n::save(os, video_stream); s11n::save(os, audio_stream); + s11n::save(os, subtitles_stream); s11n::save(os, benchmark); s11n::save(os, fullscreen); s11n::save(os, center); @@ -85,6 +87,7 @@ void player_init_data::load(std::istream &is) s11n::load(is, urls); s11n::load(is, video_stream); s11n::load(is, audio_stream); + s11n::load(is, subtitles_stream); s11n::load(is, benchmark); s11n::load(is, fullscreen); s11n::load(is, center); @@ -736,6 +739,38 @@ void player::receive_cmd(const command &cmd) } } break; + case command::cycle_subtitles_stream: + if (_media_input->subtitles_streams() > 1) + { + int oldstream = _media_input->selected_subtitles_stream(); + int newstream = oldstream + 1; + if (newstream >= _media_input->subtitles_streams()) + { + newstream = 0; + } + _media_input->select_subtitles_stream(newstream); + notify(notification::subtitles_stream, oldstream, newstream); + _seek_request = -1; // Get position right + } + break; + case command::set_subtitles_stream: + if (_media_input->subtitles_streams() > 1) + { + int oldstream = _media_input->selected_subtitles_stream(); + int newstream; + s11n::load(p, newstream); + if (newstream < 0 || newstream >= _media_input->subtitles_streams()) + { + newstream = 0; + } + if (newstream != oldstream) + { + _media_input->select_subtitles_stream(newstream); + notify(notification::subtitles_stream, oldstream, newstream); + _seek_request = -1; // Get position right + } + } + break; case command::set_stereo_layout: { int stereo_layout; @@ -893,6 +928,35 @@ void player::receive_cmd(const command &cmd) parameters_changed = true; notify(notification::ghostbust, oldval, _params.ghostbust); break; + case command::set_subtitles_font: + { + std::string old_file; + old_file = _params.subtitles_font; + _params.subtitles_font = cmd.param; + parameters_changed = true; + notify(notification::subtitles_font, old_file, _params.subtitles_font); + break; + } + case command::set_subtitles_size: + { + int val, old_val; + s11n::load(p, val); + old_val = _params.subtitles_size; + _params.subtitles_size = std::max(std::min(val, 150), 1); + parameters_changed = true; + notify(notification::subtitles_size, old_val, _params.subtitles_size); + break; + } + case command::set_subtitles_color: + { + int val, old_val; + s11n::load(p, val); + old_val = _params.subtitles_color; + _params.subtitles_color = val & 0x00FFFFFF; + parameters_changed = true; + notify(notification::subtitles_color, old_val, _params.subtitles_color); + break; + } } if (parameters_changed && _video_output) diff --git a/src/player.h b/src/player.h index 24e8d35..4d46778 100644 --- a/src/player.h +++ b/src/player.h @@ -44,6 +44,7 @@ public: std::vector urls; // Input media objects int video_stream; // Selected video stream int audio_stream; // Selected audio stream + int subtitles_stream; // Selected audio stream bool benchmark; // Benchmark mode? bool fullscreen; // Make video fullscreen? bool center; // Center video on screen? diff --git a/src/player_qt.cpp b/src/player_qt.cpp index 7c67744..bfb2744 100644 --- a/src/player_qt.cpp +++ b/src/player_qt.cpp @@ -47,6 +47,9 @@ #include #include #include +#include +#include +#include #include "player_qt.h" #include "qt_app.h" @@ -163,6 +166,17 @@ in_out_widget::in_out_widget(QSettings *settings, const player_qt_internal *play connect(_input_combobox, SIGNAL(currentIndexChanged(int)), this, SLOT(input_changed())); layout1->addWidget(_input_combobox, 0, 1); layout1->setColumnStretch(1, 1); + + QLabel *subtitles_label = new QLabel("Subtitles:"); + subtitles_label->setToolTip( + "

Select the subtitles stream.

"); + layout1->addWidget(subtitles_label, 0, 2); + _subtitles_combobox = new QComboBox(this); + _subtitles_combobox->setToolTip(subtitles_label->toolTip()); + connect(_subtitles_combobox, SIGNAL(currentIndexChanged(int)), this, SLOT(subtitles_changed())); + layout1->addWidget(_subtitles_combobox, 0, 3); + layout1->setColumnStretch(1, 1); + layout1->setColumnStretch(3, 1); QGridLayout *layout2 = new QGridLayout; QLabel *output_label = new QLabel("Output:"); @@ -216,9 +230,11 @@ in_out_widget::in_out_widget(QSettings *settings, const player_qt_internal *play input_label->setMinimumSize(output_label->minimumSizeHint()); audio_label->setMinimumSize(output_label->minimumSizeHint()); video_label->setMinimumSize(output_label->minimumSizeHint()); + subtitles_label->setMinimumSize(output_label->minimumSizeHint()); _video_combobox->setEnabled(false); _audio_combobox->setEnabled(false); + _subtitles_combobox->setEnabled(false); _input_combobox->setEnabled(false); _output_combobox->setEnabled(false); _swap_checkbox->setEnabled(false); @@ -357,6 +373,14 @@ void in_out_widget::audio_changed() } } +void in_out_widget::subtitles_changed() +{ + if (!_lock) + { + send_cmd(command::set_subtitles_stream, _subtitles_combobox->currentIndex()); + } +} + void in_out_widget::input_changed() { video_frame::stereo_layout_t stereo_layout; @@ -420,11 +444,13 @@ void in_out_widget::update(const player_init_data &init_data, bool have_valid_in _lock = true; _video_combobox->setEnabled(have_valid_input); _audio_combobox->setEnabled(have_valid_input); + _subtitles_combobox->setEnabled(have_valid_input); _input_combobox->setEnabled(have_valid_input); _output_combobox->setEnabled(have_valid_input); _swap_checkbox->setEnabled(have_valid_input); _video_combobox->clear(); _audio_combobox->clear(); + _subtitles_combobox->clear(); if (have_valid_input) { for (int i = 0; i < _player->get_media_input().video_streams(); i++) @@ -435,8 +461,13 @@ void in_out_widget::update(const player_init_data &init_data, bool have_valid_in { _audio_combobox->addItem(_player->get_media_input().audio_stream_name(i).c_str()); } + for (int i = 0; i < _player->get_media_input().subtitles_streams(); i++) + { + _subtitles_combobox->addItem(_player->get_media_input().subtitles_stream_name(i).c_str()); + } _video_combobox->setCurrentIndex(init_data.video_stream); _audio_combobox->setCurrentIndex(init_data.audio_stream); + _subtitles_combobox->setCurrentIndex(init_data.subtitles_stream); // Disable unsupported input modes for (int i = 0; i < _input_combobox->count(); i++) { @@ -473,6 +504,11 @@ int in_out_widget::get_audio_stream() return _audio_combobox->currentIndex(); } +int in_out_widget::get_subtitles_stream() +{ + return _subtitles_combobox->currentIndex(); +} + void in_out_widget::get_stereo_layout(video_frame::stereo_layout_t &stereo_layout, bool &stereo_layout_swap) { switch (_input_combobox->currentIndex()) @@ -632,6 +668,12 @@ void in_out_widget::receive_notification(const notification ¬e) _audio_combobox->setCurrentIndex(stream); _lock = false; break; + case notification::subtitles_stream: + s11n::load(current, stream); + _lock = true; + _subtitles_combobox->setCurrentIndex(stream); + _lock = false; + break; case notification::stereo_mode_swap: s11n::load(current, flag); _lock = true; @@ -1239,11 +1281,126 @@ void stereoscopic_dialog::receive_notification(const notification ¬e) } + +subtitles_dialog::subtitles_dialog(const parameters ¶ms, QWidget *parent) : QDialog(parent), +_lock(false) +{ + setModal(false); + setWindowTitle("Subtitles Settings"); + + QLabel *font_label = new QLabel("Font:"); + _font_label = new QLineEdit(params.subtitles_font.c_str()); + _font_button = new QPushButton("Browse..."); + connect(_font_button, SIGNAL(pressed()), this, SLOT(font_button_pushed())); + + QLabel *font_size_label = new QLabel("Font Size:"); + _font_size_spinbox = new QSpinBox(); + _font_size_spinbox->setRange(1, 150); + _font_size_spinbox->setValue(params.subtitles_size); + connect(_font_size_spinbox, SIGNAL(valueChanged(int)), this, SLOT(font_size_changed(int))); + + QLabel *color_label = new QLabel("Color:"); + _color_box = new QLabel(""); + _color_box->setAutoFillBackground(true); + _color_box->setMinimumSize(50, 20); + set_font_color(params.subtitles_color); + //connect(_color_box, SIGNAL(QLabel), this, SLOT(color_button_pushed())); + + _color_button = new QPushButton("Set color..."); + connect(_color_button, SIGNAL(pressed()), this, SLOT(color_button_pushed())); + + _color_dialog = new QColorDialog(); + _color_dialog->setCurrentColor(QColor(QRgb(params.subtitles_color))); + + QGridLayout *layout = new QGridLayout; + layout->addWidget(font_label, 0, 0); + layout->addWidget(_font_label, 0, 1); + layout->addWidget(_font_button, 0, 2); + layout->addWidget(font_size_label, 1, 0); + layout->addWidget(_font_size_spinbox, 1, 1); + layout->addWidget(color_label, 2, 0); + layout->addWidget(_color_box, 2, 1); + layout->addWidget(_color_button, 2, 2); + + setLayout(layout); +} + +void subtitles_dialog::font_button_pushed() +{ + std::string file = QFileDialog::getOpenFileName(this, "Open Font", "", "Font Files (*.ttf)").toStdString(); + if(!file.empty()) + { + _font_label->setText(file.c_str()); + + if (!_lock) + { + send_cmd(command::set_subtitles_font, file.c_str()); + } + } +} + +void subtitles_dialog::color_button_pushed() +{ + if(_color_dialog->exec()) + { + set_font_color(_color_dialog->currentColor().rgb()); + + if (!_lock) + { + send_cmd(command::set_subtitles_color, static_cast(_color_dialog->currentColor().rgb())); + } + } +} + +void subtitles_dialog::font_size_changed(int value) +{ + if (!_lock) + { + send_cmd(command::set_subtitles_size, value); + } +} + +void subtitles_dialog::set_font_color(int rgb) +{ + QPalette palette = _color_box->palette(); + palette.setColor(QPalette::Window, QColor(QRgb(rgb))); + _color_box->setPalette(palette); +} + +void subtitles_dialog::receive_notification(const notification ¬e) +{ + std::istringstream current(note.current); + int value; + std::string s_value; + + switch (note.type) + { + case notification::subtitles_font: + _font_label->setText(note.current.c_str()); + break; + case notification::subtitles_size: + s11n::load(current, value); + _font_size_spinbox->setValue(value); + break; + case notification::subtitles_color: + s11n::load(current, value); + _lock = true; + set_font_color(value); + _lock = false; + break; + default: + /* not handled */ + break; + } +} + + main_window::main_window(QSettings *settings, const player_init_data &init_data) : _settings(settings), _color_dialog(NULL), _crosstalk_dialog(NULL), _stereoscopic_dialog(NULL), + _subtitles_dialog(NULL), _player(NULL), _init_data(init_data), _init_data_template(init_data), @@ -1283,6 +1440,10 @@ main_window::main_window(QSettings *settings, const player_init_data &init_data) { _init_data.params.crosstalk_b = _settings->value("crosstalk_b", QString("0")).toFloat(); } + if (_init_data.params.subtitles_font.empty()) + { + _init_data.params.subtitles_font = _settings->value("subtitles_font", QString("Arial.ttf")).toString().toStdString(); + } _settings->endGroup(); _init_data.params.set_defaults(); @@ -1333,6 +1494,9 @@ main_window::main_window(QSettings *settings, const player_init_data &init_data) QAction *preferences_stereoscopic_act = new QAction(tr("Stereoscopic Video Settings..."), this); connect(preferences_stereoscopic_act, SIGNAL(triggered()), this, SLOT(preferences_stereoscopic())); preferences_menu->addAction(preferences_stereoscopic_act); + QAction *preferences_subtitles_act = new QAction(tr("Subtitles Settings..."), this); + connect(preferences_subtitles_act, SIGNAL(triggered()), this, SLOT(preferences_subtitles())); + preferences_menu->addAction(preferences_subtitles_act); QMenu *help_menu = menuBar()->addMenu(tr("&Help")); QAction *help_manual_act = new QAction(tr("&Manual..."), this); help_manual_act->setShortcut(QKeySequence::HelpContents); @@ -1464,6 +1628,13 @@ void main_window::receive_notification(const notification ¬e) _settings->setValue("audio-stream", QVariant(_init_data.audio_stream).toString()); _settings->endGroup(); break; + + case notification::subtitles_stream: + s11n::load(current, _init_data.subtitles_stream); + _settings->beginGroup("Video/" + current_file_hash()); + _settings->setValue("subtitles-stream", QVariant(_init_data.subtitles_stream).toString()); + _settings->endGroup(); + break; case notification::contrast: s11n::load(current, _init_data.params.contrast); @@ -1505,6 +1676,24 @@ void main_window::receive_notification(const notification ¬e) _settings->setValue("ghostbust", QVariant(_init_data.params.ghostbust).toString()); _settings->endGroup(); break; + + case notification::subtitles_color: + s11n::load(current, _init_data.params.subtitles_color); + _settings->beginGroup("Video/" + current_file_hash()); + _settings->setValue("subtitles_color", QVariant(_init_data.params.subtitles_color).toString()); + _settings->endGroup(); + break; + + case notification::subtitles_font: + _init_data.params.subtitles_font = note.current; + break; + + case notification::subtitles_size: + s11n::load(current, _init_data.params.subtitles_size); + _settings->beginGroup("Video/" + current_file_hash()); + _settings->setValue("subtitles_size", QVariant(_init_data.params.subtitles_size).toString()); + _settings->endGroup(); + break; case notification::pause: case notification::stereo_layout: @@ -1563,6 +1752,7 @@ void main_window::closeEvent(QCloseEvent *event) _settings->setValue("crosstalk_r", QVariant(_init_data.params.crosstalk_r).toString()); _settings->setValue("crosstalk_g", QVariant(_init_data.params.crosstalk_g).toString()); _settings->setValue("crosstalk_b", QVariant(_init_data.params.crosstalk_b).toString()); + _settings->setValue("subtitles_font", QVariant(_init_data.params.subtitles_font.c_str()).toString()); _settings->endGroup(); event->accept(); } @@ -1647,6 +1837,8 @@ void main_window::open(QStringList filenames) _init_data.audio_stream = std::max(0, std::min(_init_data.audio_stream, _player->get_media_input().audio_streams() - 1)); _init_data.params.parallax = QVariant(_settings->value("parallax", QVariant(_init_data.params.parallax)).toString()).toFloat(); _init_data.params.ghostbust = QVariant(_settings->value("ghostbust", QVariant(_init_data.params.ghostbust)).toString()).toFloat(); + _init_data.params.subtitles_color = QVariant(_settings->value("subtitles_color", QVariant(_init_data.params.subtitles_color)).toString()).toInt(); + _init_data.params.subtitles_size = QVariant(_settings->value("subtitles_size", QVariant(_init_data.params.subtitles_size)).toString()).toInt(); // Get stereo mode for this video _settings->endGroup(); QString mode_fallback = QString(parameters::stereo_mode_to_string( @@ -1750,6 +1942,17 @@ void main_window::preferences_stereoscopic() _stereoscopic_dialog->activateWindow(); } +void main_window::preferences_subtitles() +{ + if (!_subtitles_dialog) + { + _subtitles_dialog = new subtitles_dialog(_init_data.params, this); + } + _subtitles_dialog->show(); + _subtitles_dialog->raise(); + _subtitles_dialog->activateWindow(); +} + void main_window::help_manual() { QUrl manual_url; diff --git a/src/player_qt.h b/src/player_qt.h index f631a08..3fc843b 100644 --- a/src/player_qt.h +++ b/src/player_qt.h @@ -33,10 +33,13 @@ #include #include #include +#include +#include #include "controller.h" #include "video_output_qt.h" #include "player.h" +#include class player_qt_internal : public player, public controller @@ -73,6 +76,7 @@ private: const player_qt_internal *_player; QComboBox *_video_combobox; QComboBox *_audio_combobox; + QComboBox *_subtitles_combobox; QComboBox *_input_combobox; QComboBox *_output_combobox; QCheckBox *_swap_checkbox; @@ -84,6 +88,7 @@ private: private slots: void video_changed(); void audio_changed(); + void subtitles_changed(); void input_changed(); void output_changed(); void swap_changed(); @@ -96,6 +101,7 @@ public: int get_video_stream(); int get_audio_stream(); + int get_subtitles_stream(); void get_stereo_layout(video_frame::stereo_layout_t &stereo_layout, bool &stereo_layout_swap); void get_stereo_mode(parameters::stereo_mode_t &stereo_mode, bool &stereo_mode_swap); @@ -202,6 +208,7 @@ class stereoscopic_dialog : public QDialog, public controller private: bool _lock; + QDoubleSpinBox *_p_spinbox; QSlider *_p_slider; QDoubleSpinBox *_g_spinbox; @@ -219,6 +226,33 @@ public: virtual void receive_notification(const notification ¬e); }; +class subtitles_dialog : public QDialog, public controller +{ + Q_OBJECT + +private: + bool _lock; + QLineEdit *_font_label; + QPushButton * _font_button; + QColorDialog * _color_dialog; + QSpinBox * _font_size_spinbox; + QLabel * _color_box; + QPushButton * _color_button; + +private slots: + void font_button_pushed(); + void color_button_pushed(); + void font_size_changed(int value); + +private: + void set_font_color(int rgb); + +public: + subtitles_dialog(const parameters ¶ms, QWidget *parent); + + virtual void receive_notification(const notification ¬e); +}; + class main_window : public QMainWindow, public controller { @@ -232,6 +266,7 @@ private: color_dialog *_color_dialog; crosstalk_dialog *_crosstalk_dialog; stereoscopic_dialog *_stereoscopic_dialog; + subtitles_dialog *_subtitles_dialog; player_qt_internal *_player; QTimer *_timer; player_init_data _init_data; @@ -250,6 +285,7 @@ private slots: void preferences_colors(); void preferences_crosstalk(); void preferences_stereoscopic(); + void preferences_subtitles(); void help_manual(); void help_website(); void help_keyboard(); diff --git a/src/video_output.cpp b/src/video_output.cpp index 34383f4..4cd2db9 100644 --- a/src/video_output.cpp +++ b/src/video_output.cpp @@ -37,6 +37,9 @@ #include "video_output_render.fs.glsl.h" #include "xgl.h" +#ifdef HAVE_LIBFTGL +#include +#endif /* Video output overview: * @@ -80,8 +83,11 @@ * interpolation, too. */ + video_output::video_output(bool receive_notifications) : controller(receive_notifications), + subtitles_font(NULL), + subtitles_layout(NULL), _initialized(false) { // XXX: Hack: work around broken SRGB texture implementations @@ -104,10 +110,22 @@ video_output::video_output(bool receive_notifications) : _color_fbo = 0; _render_prg = 0; _render_mask_tex = 0; + +#ifdef HAVE_LIBFTGL + // Set layout properties + subtitles_layout = new FTSimpleLayout(); + subtitles_layout->SetAlignment(FTGL::ALIGN_CENTER); + subtitles_layout->SetLineLength(100); // Will be overwriten on resize + subtitles_layout->SetLineSpacing(0.7); +#endif } video_output::~video_output() { + if(subtitles_font) + delete subtitles_font; + if(subtitles_layout) + delete subtitles_layout; } void video_output::init() @@ -174,6 +192,8 @@ void video_output::set_suitable_size(int w, int h, float ar, parameters::stereo_ height = max_height; } trigger_resize(width, height); + + subtitles_layout->SetLineLength(width); } void video_output::input_init(int index, const video_frame &frame) @@ -313,6 +333,9 @@ void video_output::prepare_next_frame(const video_frame &frame) input_init(index, frame); _frame[index] = frame; } + //Make sure subtitles are copied + _frame[index].subtitle = frame.subtitle; + int bytes_per_pixel = (frame.layout == video_frame::bgra32 ? 4 : 1); GLenum format = (frame.layout == video_frame::bgra32 ? GL_BGRA : GL_LUMINANCE); GLenum type = (frame.layout == video_frame::bgra32 ? GL_UNSIGNED_INT_8_8_8_8_REV : GL_UNSIGNED_BYTE); @@ -572,6 +595,27 @@ void video_output::activate_next_frame() void video_output::set_parameters(const parameters ¶ms) { + if(subtitles_layout) + { + if(!subtitles_font || params.subtitles_font != _params.subtitles_font) + { + if(subtitles_font) + delete subtitles_font; + subtitles_font = new FTBitmapFont(params.subtitles_font.c_str()); + if(subtitles_font->Error()) + { + msg::wrn("Unable to load subtitles font: %s", params.subtitles_font.c_str()); + delete subtitles_font; + subtitles_font = 0; + } + } + if(subtitles_font) + { + subtitles_font->FaceSize(params.subtitles_size); + subtitles_layout->SetFont(subtitles_font); + } + } + _params = params; bool context_needs_stereo = (_params.stereo_mode == parameters::stereo); if (context_needs_stereo != context_is_stereo()) @@ -857,6 +901,24 @@ void video_output::display_current_frame(bool mono_right_instead_of_left, draw_quad(-1.0f, -1.0f, 2.0f, 1.0f); } assert(xgl::CheckError(HERE)); + + if(subtitles_font && frame.subtitle) + { + // If something went wrong, bail out. + if(subtitles_font->Error()) + { + msg::err(str::from("Error in FTGL library!!!")); + return; + } + + FTBBox box = subtitles_layout->BBox(frame.subtitle->text.c_str()); + glWindowPos2f(0, box.Upper().Y()-box.Lower().Y()); + GLfloat old_color[4]; + glGetFloatv(GL_CURRENT_COLOR, old_color); + glColor3i(_params.subtitles_color&&0xFF, _params.subtitles_color&&0xFF00, _params.subtitles_color&&0xFF0000); + subtitles_layout->Render(frame.subtitle->text.c_str()); + glColor4fv(old_color); + } } void video_output::clear() diff --git a/src/video_output.h b/src/video_output.h index 6c64234..7371912 100644 --- a/src/video_output.h +++ b/src/video_output.h @@ -27,9 +27,12 @@ #include + #include "media_data.h" #include "controller.h" +class FTBitmapFont; +class FTSimpleLayout; class video_output : public controller { @@ -64,6 +67,7 @@ private: GLuint _render_mask_tex; // for the masking modes even-odd-{rows,columns}, checkerboard // OpenGL Viewport for drawing the video frame GLint _viewport[4]; + private: // Step 1: initialize/deinitialize, and check if reinitialization is necessary @@ -102,6 +106,9 @@ protected: display_current_frame(false, -1.0f, -1.0f, 2.0f, 2.0f, _viewport); } + FTBitmapFont *subtitles_font; + FTSimpleLayout *subtitles_layout; + public: /* Constructor, Destructor */ video_output(bool receive_notifications = true); diff --git a/src/video_output_qt.cpp b/src/video_output_qt.cpp index d2a410c..ac53feb 100644 --- a/src/video_output_qt.cpp +++ b/src/video_output_qt.cpp @@ -542,10 +542,13 @@ void video_output_qt::process_events() void video_output_qt::receive_notification(const notification ¬e) { - if (note.type == notification::play) + std::istringstream current(note.current); + + switch(note.type) { - std::istringstream current(note.current); + case notification::play: s11n::load(current, _playing); + break; } /* More is currently not implemented. * In the future, an on-screen display might show hints about what happened. */