mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-05 15:30:00 +01:00
262 lines
7.0 KiB
C++
262 lines
7.0 KiB
C++
/*
|
|
* This file is part of vimix - video live mixer
|
|
*
|
|
* **Copyright** (C) 2020-2021 Bruno Herbelin <bruno.herbelin@gmail.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, see <https://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
|
|
#include "defines.h"
|
|
#include "ImageShader.h"
|
|
#include "Resource.h"
|
|
#include "Decorations.h"
|
|
#include "Stream.h"
|
|
#include "Visitor.h"
|
|
#include "GstToolkit.h"
|
|
#include "BaseToolkit.h"
|
|
#include "SystemToolkit.h"
|
|
#include "MediaPlayer.h"
|
|
#include "Log.h"
|
|
|
|
#include "MultiFileSource.h"
|
|
|
|
// example test gstreamer pipelines
|
|
//
|
|
// multifile : sequence of numbered images
|
|
// gst-launch-1.0 multifilesrc location="/home/bhbn/Images/sequence/frames%03d.png" caps="image/png,framerate=\(fraction\)12/1" loop=1 ! decodebin ! videoconvert ! autovideosink
|
|
//
|
|
// imagesequencesrc : sequence of numbered images (cannot loop)
|
|
// gst-launch-1.0 imagesequencesrc location=frames%03d.png start-index=1 framerate=24/1 ! decodebin ! videoconvert ! autovideosink
|
|
//
|
|
|
|
MultiFileSequence::MultiFileSequence() : width(0), height(0), min(0), max(0)
|
|
{
|
|
}
|
|
|
|
MultiFileSequence::MultiFileSequence(const std::list<std::string> &list_files)
|
|
{
|
|
location = BaseToolkit::common_numbered_pattern(list_files, &min, &max);
|
|
|
|
// sanity check: the location pattern looks like a filename and seems consecutive numbered
|
|
if ( SystemToolkit::extension_filename(location).empty() ||
|
|
SystemToolkit::path_filename(location) != SystemToolkit::path_filename(list_files.front()) ||
|
|
list_files.size() != max - min + 1 ) {
|
|
Log::Info("MultiFileSequence '%s' invalid.", location.c_str());
|
|
location.clear();
|
|
}
|
|
|
|
if ( !location.empty() ) {
|
|
MediaInfo media = MediaPlayer::UriDiscoverer( GstToolkit::filename_to_uri( list_files.front() ) );
|
|
if (media.valid && media.isimage) {
|
|
codec.resize(media.codec_name.size());
|
|
std::transform(media.codec_name.begin(), media.codec_name.end(), codec.begin(), ::tolower);
|
|
width = media.width;
|
|
height = media.height;
|
|
}
|
|
else
|
|
Log::Info("MultiFileSequence '%s' does not list images.", location.c_str());
|
|
}
|
|
}
|
|
|
|
bool MultiFileSequence::valid() const
|
|
{
|
|
return !( location.empty() || codec.empty() || width < 1 || height < 1 || max == min);
|
|
}
|
|
|
|
inline MultiFileSequence& MultiFileSequence::operator = (const MultiFileSequence& b)
|
|
{
|
|
if (this != &b) {
|
|
this->width = b.width;
|
|
this->height = b.height;
|
|
this->min = b.min;
|
|
this->max = b.max;
|
|
this->location = b.location;
|
|
this->codec = b.codec;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
|
|
bool MultiFileSequence::operator != (const MultiFileSequence& b)
|
|
{
|
|
return ( location != b.location || codec != b.codec || width != b.width ||
|
|
height != b.height || min != b.min || max != b.max );
|
|
}
|
|
|
|
MultiFile::MultiFile() : Stream(), src_(nullptr)
|
|
{
|
|
|
|
}
|
|
|
|
void MultiFile::open (const MultiFileSequence &sequence, uint framerate )
|
|
{
|
|
if (sequence.location.empty())
|
|
return;
|
|
|
|
std::ostringstream gstreamer_pipeline;
|
|
gstreamer_pipeline << "multifilesrc name=src location=\"";
|
|
gstreamer_pipeline << sequence.location;
|
|
gstreamer_pipeline << "\" caps=\"image/";
|
|
gstreamer_pipeline << sequence.codec;
|
|
gstreamer_pipeline << ",framerate=(fraction)";
|
|
gstreamer_pipeline << framerate;
|
|
gstreamer_pipeline << "/1\" loop=1";
|
|
gstreamer_pipeline << " start-index=";
|
|
gstreamer_pipeline << sequence.min;
|
|
gstreamer_pipeline << " stop-index=";
|
|
gstreamer_pipeline << sequence.max;
|
|
gstreamer_pipeline << " ! decodebin ! videoconvert";
|
|
|
|
// (private) open stream
|
|
Stream::open(gstreamer_pipeline.str(), sequence.width, sequence.height);
|
|
|
|
// keep multifile source for dynamic properties change
|
|
src_ = gst_bin_get_by_name (GST_BIN (pipeline_), "src");
|
|
}
|
|
|
|
void MultiFile::close ()
|
|
{
|
|
if (src_ != nullptr) {
|
|
gst_object_unref (src_);
|
|
src_ = nullptr;
|
|
}
|
|
Stream::close();
|
|
}
|
|
|
|
void MultiFile::setIndex(int val)
|
|
{
|
|
if (src_) {
|
|
g_object_set (src_, "index", val, NULL);
|
|
}
|
|
}
|
|
|
|
int MultiFile::index()
|
|
{
|
|
int val = 0;
|
|
if (src_) {
|
|
g_object_get (src_, "index", &val, NULL);
|
|
}
|
|
return val;
|
|
}
|
|
|
|
void MultiFile::setProperties (int begin, int end, int loop)
|
|
{
|
|
if (src_) {
|
|
g_object_set (src_, "start-index", MAX(begin, 0), NULL);
|
|
g_object_set (src_, "stop-index", MAX(end, 0), NULL);
|
|
g_object_set (src_, "loop", MIN(loop, 1), NULL);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
MultiFileSource::MultiFileSource (uint64_t id) : StreamSource(id), framerate_(0), begin_(-1), end_(INT_MAX), loop_(1)
|
|
{
|
|
// create stream
|
|
stream_ = static_cast<Stream *>( new MultiFile );
|
|
|
|
// set symbol
|
|
symbol_ = new Symbol(Symbol::SEQUENCE, glm::vec3(0.75f, 0.75f, 0.01f));
|
|
symbol_->scale_.y = 1.5f;
|
|
}
|
|
|
|
|
|
void MultiFileSource::setFiles (const std::list<std::string> &list_files, uint framerate)
|
|
{
|
|
setSequence(MultiFileSequence(list_files), framerate);
|
|
}
|
|
|
|
void MultiFileSource::setSequence (const MultiFileSequence &sequence, uint framerate)
|
|
{
|
|
framerate_ = CLAMP( framerate, 1, 30);
|
|
sequence_ = sequence;
|
|
|
|
if (sequence_.valid())
|
|
{
|
|
// open gstreamer
|
|
multifile()->open( sequence_, framerate_ );
|
|
stream_->play(true);
|
|
|
|
// validate range and apply loop_
|
|
setRange(begin_, end_);
|
|
|
|
// will be ready after init and one frame rendered
|
|
ready_ = false;
|
|
}
|
|
}
|
|
|
|
void MultiFileSource::setFramerate (uint framerate)
|
|
{
|
|
if (multifile()) {
|
|
setSequence(sequence_, framerate);
|
|
}
|
|
}
|
|
|
|
void MultiFileSource::setLoop (bool on)
|
|
{
|
|
if (multifile()) {
|
|
loop_ = on ? 1 : 0;
|
|
multifile()->setProperties (begin_, end_, loop_);
|
|
}
|
|
}
|
|
|
|
void MultiFileSource::setRange (int begin, int end)
|
|
{
|
|
begin_ = glm::clamp( begin, sequence_.min, sequence_.max );
|
|
end_ = glm::clamp( end , sequence_.min, sequence_.max );
|
|
begin_ = glm::min( begin_, end_ );
|
|
end_ = glm::max( begin_, end_ );
|
|
|
|
if (multifile())
|
|
multifile()->setProperties (begin_, end_, loop_);
|
|
}
|
|
|
|
void MultiFileSource::replay ()
|
|
{
|
|
if (multifile()) {
|
|
multifile()->setIndex (begin_);
|
|
stream_->rewind();
|
|
}
|
|
}
|
|
|
|
guint64 MultiFileSource::playtime () const
|
|
{
|
|
guint64 time = 0;
|
|
|
|
if (multifile())
|
|
time += multifile()->index();
|
|
|
|
time *= GST_SECOND;
|
|
time /= framerate_;
|
|
|
|
return time;
|
|
}
|
|
|
|
void MultiFileSource::accept (Visitor& v)
|
|
{
|
|
Source::accept(v);
|
|
if (!failed())
|
|
v.visit(*this);
|
|
}
|
|
|
|
MultiFile *MultiFileSource::multifile () const
|
|
{
|
|
return dynamic_cast<MultiFile *>(stream_);
|
|
}
|
|
|
|
|