| /** |
| * Copyright (C) 2022 Savoir-faire Linux Inc. |
| * |
| * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program 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 General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #include "TranscriptVideoSubscriber.h" |
| |
| extern "C" { |
| #include <libavutil/display.h> |
| } |
| |
| #include <pluglog.h> |
| #include <mediaStream.h> |
| #include <frameScaler.h> |
| #include <accel.h> |
| #include <common.h> |
| #include <frameUtils.h> |
| |
| #include <fmt/core.h> |
| #include <fmt/format.h> |
| |
| #include <bitset> |
| #include <string_view> |
| |
| using namespace std::literals; |
| |
| const std::string TAG = "TranscriptVideo"; |
| const char sep = separator(); |
| |
| namespace jami { |
| |
| TranscriptVideoSubscriber::TranscriptVideoSubscriber(const std::string& dataPath) |
| : path_ {dataPath} |
| { |
| fontFile_ = string_utils::ffmpegFormatString(dataPath + sep + "Muli-Light.ttf"); |
| } |
| |
| TranscriptVideoSubscriber::~TranscriptVideoSubscriber() |
| { |
| filter_.clean(); |
| Plog::log(Plog::LogPriority::INFO, TAG, "~TranscriptMediaProcessor"); |
| } |
| |
| void |
| TranscriptVideoSubscriber::setText(const std::string& t) |
| { |
| Plog::log(Plog::LogPriority::INFO, TAG, "setText " + t); |
| auto text = string_utils::ffmpegScapeString(t); |
| std::vector<std::string> textWords = string_utils::getWords(text, " "); |
| subtitle_ = ""; |
| |
| auto idx = 0; |
| for (const auto& word : textWords) { |
| idx++; |
| subtitle_ = fmt::format("{} {}", subtitle_, word); |
| if (idx % maxWordsLength == 0) |
| subtitle_ = fmt::format("{}{}", subtitle_, "\n"); |
| } |
| |
| #ifdef __DEBUG__ |
| Plog::log(Plog::LogPriority::INFO, TAG, subtitle_); |
| #endif |
| firstRun = true; |
| } |
| |
| void |
| TranscriptVideoSubscriber::setParameter(std::string& parameter, Parameter type) |
| { |
| switch (type) { |
| case (Parameter::FONTSIZE): |
| fontSize_ = parameter; |
| break; |
| case (Parameter::BACKGROUND): |
| background_ = parameter; |
| if (background_.find("black") == std::string::npos) { |
| fontColor_ = "black"; |
| fontBackground_ = "white@0.5"; |
| } else { |
| fontColor_ = "white"; |
| fontBackground_ = "black@0.5"; |
| } |
| break; |
| case (Parameter::POSITION): |
| position_ = parameter; |
| break; |
| default: |
| return; |
| } |
| |
| firstRun = true; |
| } |
| |
| std::string_view getTransposeDescr(int rotation) |
| { |
| switch (rotation) { |
| case 90: |
| return "transpose=1,"sv; |
| case 180: |
| return "transpose=1, transpose=1,"sv; |
| case -180: |
| return "transpose=2, transpose=2,"sv; |
| case -90: |
| return "transpose=2,"sv; |
| default: |
| return {}; |
| } |
| return {}; |
| } |
| |
| void |
| TranscriptVideoSubscriber::setFilterDescription() |
| { |
| Plog::log(Plog::LogPriority::INFO, TAG, "setFilterDescription() " + subtitle_); |
| if (pluginFrameSize_.first == 0 || pluginFrameSize_.second == 0) |
| return; |
| |
| // 1, 2, 3, and 4 are cartesian positions |
| int margin = 10; |
| if (position_ == "1") { |
| point_ = {pluginFrameSize_.first - margin, margin}; |
| } else if (position_ == "2") { |
| point_ = {margin, margin}; |
| } else if (position_ == "3") { |
| point_ = {margin, pluginFrameSize_.second - margin}; |
| } else if (position_ == "4") { |
| point_ = {pluginFrameSize_.first - margin, pluginFrameSize_.second - margin}; |
| } |
| |
| auto baseInfosDescription |
| = fmt::format("[input]{}" |
| "drawtext=fontcolor={}:fontsize={}:fontfile=\\'{}\\':expansion=none:text='{}" |
| "':line_spacing=5:box=1:boxcolor={}:boxborderw=5:x=", |
| getTransposeDescr(angle_), |
| fontColor_, fontSize_, fontFile_, subtitle_, fontBackground_); |
| |
| auto position = "{}-text_w:y={}"sv; |
| if (position_ == "2") |
| position = "{}:y={}"sv; |
| else if (position_ == "3") |
| position = "{}:y={}-text_h"sv; |
| else if (position_ == "4") |
| position = "{}-text_w:y={}-text_h"sv; |
| filterDescription_ = baseInfosDescription + fmt::format(std::string(position) + ",{}format=yuv420p"s, |
| point_.first, |
| point_.second, |
| getTransposeDescr(-angle_)); |
| |
| Plog::log(Plog::LogPriority::INFO, TAG, filterDescription_); |
| } |
| |
| void |
| TranscriptVideoSubscriber::update(jami::Observable<AVFrame*>*, AVFrame* const& pluginFrame) |
| { |
| if (!observable_ || !pluginFrame || subtitle_.empty()) |
| return; |
| |
| int newAngle {0}; |
| if (AVFrameSideData* side_data = av_frame_get_side_data(pluginFrame, AV_FRAME_DATA_DISPLAYMATRIX)) { |
| auto matrix_rotation = reinterpret_cast<int32_t*>(side_data->data); |
| newAngle = static_cast<int>(av_display_rotation_get(matrix_rotation)); |
| } |
| if (newAngle != angle_) { |
| angle_ = newAngle; |
| firstRun = true; |
| } |
| |
| //====================================================================================== |
| // GET RAW FRAME |
| uniqueFramePtr rgbFrame = {transferToMainMemory(pluginFrame, AV_PIX_FMT_NV12), frameFree}; |
| if (!rgbFrame.get()) |
| return; |
| if ((AVPixelFormat)rgbFrame->format != AV_PIX_FMT_YUV420P) |
| rgbFrame.reset(FrameScaler::convertFormat(rgbFrame.get(), AV_PIX_FMT_YUV420P)); |
| if (!rgbFrame.get()) |
| return; |
| |
| if (sourceTimeBase_.num != pluginFrame->time_base.num || sourceTimeBase_.den != pluginFrame->time_base.den) |
| firstRun = true; |
| |
| rgbFrame->pts = pluginFrame->pts; |
| rgbFrame->time_base = pluginFrame->time_base; |
| sourceTimeBase_ = pluginFrame->time_base; |
| |
| if (firstRun) { |
| filter_.clean(); |
| pluginFrameSize_ = {rgbFrame->width, rgbFrame->height}; |
| if (std::abs(angle_) == 90) |
| pluginFrameSize_ = {rgbFrame->height, rgbFrame->width}; |
| setFilterDescription(); |
| |
| rational<int> fr(sourceTimeBase_.den, sourceTimeBase_.num); |
| auto ms = MediaStream("input", |
| rgbFrame->format, |
| 1 / fr, |
| rgbFrame->width, |
| rgbFrame->height, |
| 0, |
| fr); |
| filter_.initialize(filterDescription_, {ms}); |
| firstRun = false; |
| } |
| |
| if (!filter_.initialized_) |
| return; |
| |
| if (filter_.feedInput(rgbFrame.get(), "input") == 0) { |
| uniqueFramePtr filteredFrame = {filter_.readOutput(), frameFree}; |
| if (filteredFrame) { |
| moveFrom(pluginFrame, filteredFrame.get()); |
| } |
| } |
| } |
| |
| void |
| TranscriptVideoSubscriber::attached(jami::Observable<AVFrame*>* observable) |
| { |
| Plog::log(Plog::LogPriority::INFO, TAG, "::Attached ! "); |
| observable_ = observable; |
| } |
| |
| void |
| TranscriptVideoSubscriber::detached(jami::Observable<AVFrame*>*) |
| { |
| firstRun = true; |
| observable_ = nullptr; |
| Plog::log(Plog::LogPriority::INFO, TAG, "::Detached()"); |
| } |
| |
| void |
| TranscriptVideoSubscriber::detach() |
| { |
| if (observable_) { |
| firstRun = true; |
| std::ostringstream oss; |
| Plog::log(Plog::LogPriority::INFO, TAG, "::Calling detach()"); |
| observable_->detach(this); |
| } |
| } |
| } // namespace jami |