/***
Olive - Non-Linear Video Editor
Copyright (C) 2020 Olive Team
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 .
***/
#include "export.h"
#include "common/timecodefunctions.h"
#include "render/colormanager.h"
namespace olive {
ExportTask::ExportTask(ViewerOutput *viewer_node,
ColorManager* color_manager,
const ExportParams& params) :
RenderTask(viewer_node, params.video_params(), params.audio_params()),
color_manager_(color_manager),
params_(params)
{
SetTitle(tr("Exporting \"%1\"").arg(viewer_node->GetLabel()));
}
bool ExportTask::Run()
{
TimeRange range;
// For safety, if we're overwriting, we save to a temporary filename and then only overwrite it
// at the end
QString real_filename = params_.filename();
if (QFileInfo::exists(params_.filename())) {
// Generate a filename that definitely doesn't exist
params_.SetFilename(FileFunctions::GetSafeTemporaryFilename(real_filename));
}
encoder_ = Encoder::CreateFromID(params_.encoder(), params_);
if (!encoder_) {
SetError(tr("Failed to create encoder"));
return false;
}
if (!encoder_->Open()) {
SetError(tr("Failed to open file"));
encoder_->deleteLater();
return false;
}
if (params_.has_custom_range()) {
// Render custom range only
range = params_.custom_range();
} else {
// Render entire sequence
range = TimeRange(0, viewer()->GetLength());
}
frame_time_ = 0;
QSize video_force_size;
QMatrix4x4 video_force_matrix;
if (params_.video_enabled()) {
// If a transformation matrix is applied to this video, create it here
VideoParams vp = viewer()->GetVideoParams();
if (vp.width() != params_.video_params().width()
|| params_.video_params().height() != params_.video_params().height()) {
video_force_size = QSize(params_.video_params().width(), params_.video_params().height());
if (params_.video_scaling_method() != ExportParams::kStretch) {
video_force_matrix = ExportParams::GenerateMatrix(params_.video_scaling_method(),
vp.width(),
vp.height(),
params_.video_params().width(),
params_.video_params().height());
}
} else {
// Disables forcing size in the renderer
video_force_size = QSize(0, 0);
}
// Create color processor
color_processor_ = ColorProcessor::Create(color_manager_,
color_manager_->GetReferenceColorSpace(),
params_.color_transform());
}
if (params_.audio_enabled()) {
audio_data_.SetParameters(audio_params());
}
// Start render process
TimeRangeList video_range, audio_range;
if (params_.video_enabled()) {
video_range = {range};
}
if (params_.audio_enabled()) {
audio_range = {range};
audio_data_.SetLength(range.length());
}
Render(color_manager_, video_range, audio_range, RenderMode::kOnline, nullptr,
video_force_size, video_force_matrix, encoder_->GetDesiredPixelFormat(),
color_processor_);
bool success = true;
if (params_.audio_enabled()) {
// Write audio data now
encoder_->WriteAudio(audio_params(), audio_data_.CreatePlaybackDevice(encoder_));
}
encoder_->Close();
delete encoder_;
// If cancelled, delete the file we made, which is always a file we created since we write to a
// temp file during the actual encoding process
if (IsCancelled()) {
QFile::remove(params_.filename());
} else if (params_.filename() != real_filename) {
// If we were writing to a temp file, overwrite now
if (!FileFunctions::RenameFileAllowOverwrite(params_.filename(), real_filename)) {
SetError(tr("Failed to overwrite \"%1\". Export has been saved as \"%2\" instead.")
.arg(real_filename, params_.filename()));
success = false;
}
}
return success;
}
void ExportTask::FrameDownloaded(FramePtr f, const QByteArray &hash, const QVector ×, qint64 job_time)
{
Q_UNUSED(job_time)
Q_UNUSED(hash)
foreach (const rational& t, times) {
rational actual_time = t;
if (params_.has_custom_range()) {
actual_time -= params_.custom_range().in();
}
time_map_.insert(actual_time, f);
}
forever {
rational real_time = Timecode::timestamp_to_time(frame_time_,
viewer()->GetVideoParams().frame_rate_as_time_base());
if (!time_map_.contains(real_time)) {
break;
}
// Unfortunately this can't be done in another thread since the frames need to be sent
// one after the other chronologically.
encoder_->WriteFrame(time_map_.take(real_time), real_time);
frame_time_++;
}
}
void ExportTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples, qint64 job_time)
{
Q_UNUSED(job_time)
TimeRange adjusted_range = range;
if (params_.has_custom_range()) {
adjusted_range -= params_.custom_range().in();
}
audio_data_.WritePCM(adjusted_range, samples, QDateTime::currentMSecsSinceEpoch());
}
}