You've already forked libopenshot
mirror of
https://github.com/OpenShot/libopenshot.git
synced 2026-03-02 08:53:52 -08:00
Improved Profile Class (Helper methods, Sortable, Unit tests) (#895)
* Removing legacy profile property. Add new operators for Profile classes (for comparison). Also added new functions to generate different variations of the Profile data (key, short name, long name, long name w/description). * Add empty constructor for Profile class, and new Profile unit tets * Adding zero padding to profile Key function, for easier sorting: 01920x1080i2997_16:09 * Clear setfill flag after creating Key() output * Updating example exe to load an *.osp project file via C++, which makes debugging complex broken projects much easier. * - Add new unit test to FFmpegWriter to create an animated GIF and verify it can be wrapped with a FrameMapper (with no audio track) - Improve FrameMapper to ignore missing audio data (i.e. when no audio samples present, don't try and find them or resample them) * Fix some whitespace issues * Fix inline documentation mistype * Fixed missing reuse licensing on new example profile files * Changing Profile::Key() format to exclude the : character, since Windows file names cannot contain that * - Large memory leak fixed in FFmpegWriter when closing the video & audio contexts - Reducing # of cached frames and rescalers to 1, since we no longer use OMP and this is unneeded - we need to refactor much of this code out eventually * - Fixing whitespace issues - Code clean-up / line wrapping / etc...
This commit is contained in:
@@ -3,7 +3,7 @@ Upstream-Name: libopenshot
|
||||
Upstream-Contact: Jonathan Thomas <jonathan@openshot.org>
|
||||
Source: https://github.com/OpenShot/libopenshot
|
||||
|
||||
Files: examples/*.png examples/*.svg examples/*.wav examples/*.mp4 examples/*.avi doc/images/*
|
||||
Files: examples/*.png examples/*.svg examples/*.wav examples/*.mp4 examples/* doc/images/*
|
||||
Copyright: OpenShot Studios, LLC
|
||||
License: LGPL-3.0-or-later
|
||||
|
||||
|
||||
@@ -13,25 +13,56 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <QFileDialog>
|
||||
#include "Clip.h"
|
||||
#include "Frame.h"
|
||||
#include "FFmpegReader.h"
|
||||
#include "Timeline.h"
|
||||
#include "Profiles.h"
|
||||
|
||||
using namespace openshot;
|
||||
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
|
||||
// FFmpeg Reader performance test
|
||||
FFmpegReader r9("/home/jonathan/Downloads/pts-test-files/broken-files/lady-talking-1.mp4");
|
||||
r9.Open();
|
||||
for (long int frame = 1; frame <= r9.info.video_length; frame++)
|
||||
{
|
||||
std::cout << "Requesting Frame: #: " << frame << std::endl;
|
||||
std::shared_ptr<Frame> f = r9.GetFrame(frame);
|
||||
QString filename = "/home/jonathan/test-crash.osp";
|
||||
//QString filename = "/home/jonathan/Downloads/drive-download-20221123T185423Z-001/project-3363/project-3363.osp";
|
||||
//QString filename = "/home/jonathan/Downloads/drive-download-20221123T185423Z-001/project-3372/project-3372.osp";
|
||||
//QString filename = "/home/jonathan/Downloads/drive-download-20221123T185423Z-001/project-3512/project-3512.osp";
|
||||
QString project_json = "";
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
std::cout << "File error!" << std::endl;
|
||||
exit(1);
|
||||
} else {
|
||||
while (!file.atEnd()) {
|
||||
QByteArray line = file.readLine();
|
||||
project_json += line;
|
||||
}
|
||||
}
|
||||
r9.Close();
|
||||
|
||||
// Open timeline reader
|
||||
std::cout << "Project JSON length: " << project_json.length() << std::endl;
|
||||
Timeline r(1280, 720, openshot::Fraction(30, 1), 44100, 2, openshot::LAYOUT_STEREO);
|
||||
r.SetJson(project_json.toStdString());
|
||||
r.DisplayInfo();
|
||||
r.Open();
|
||||
|
||||
// Get max frame
|
||||
int64_t max_frame = r.GetMaxFrame();
|
||||
std::cout << "max_frame: " << max_frame << ", r.info.video_length: " << r.info.video_length << std::endl;
|
||||
|
||||
for (long int frame = 1; frame <= max_frame; frame++)
|
||||
{
|
||||
float percent = (float(frame) / max_frame) * 100.0;
|
||||
std::cout << "Requesting Frame #: " << frame << " (" << percent << "%)" << std::endl;
|
||||
std::shared_ptr<Frame> f = r.GetFrame(frame);
|
||||
|
||||
// Preview frame image
|
||||
if (frame % 1 == 0) {
|
||||
f->Save("preview.jpg", 1.0, "jpg", 100);
|
||||
}
|
||||
}
|
||||
r.Close();
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
11
examples/example_profile1
Normal file
11
examples/example_profile1
Normal file
@@ -0,0 +1,11 @@
|
||||
description=HD 720p 24 fps
|
||||
frame_rate_num=24
|
||||
frame_rate_den=1
|
||||
width=1280
|
||||
height=720
|
||||
progressive=1
|
||||
sample_aspect_num=1
|
||||
sample_aspect_den=1
|
||||
display_aspect_num=16
|
||||
display_aspect_den=9
|
||||
colorspace=709
|
||||
11
examples/example_profile2
Normal file
11
examples/example_profile2
Normal file
@@ -0,0 +1,11 @@
|
||||
description=HD 1080i 29.97 fps
|
||||
frame_rate_num=30000
|
||||
frame_rate_den=1001
|
||||
width=1920
|
||||
height=1080
|
||||
progressive=0
|
||||
sample_aspect_num=1
|
||||
sample_aspect_den=1
|
||||
display_aspect_num=16
|
||||
display_aspect_den=9
|
||||
colorspace=709
|
||||
1103
src/FFmpegWriter.cpp
1103
src/FFmpegWriter.cpp
File diff suppressed because it is too large
Load Diff
@@ -41,6 +41,9 @@ FrameMapper::FrameMapper(ReaderBase *reader, Fraction target, PulldownType targe
|
||||
info.width = reader->info.width;
|
||||
info.height = reader->info.height;
|
||||
|
||||
// Enable/Disable audio (based on settings)
|
||||
info.has_audio = info.sample_rate > 0 && info.channels > 0;
|
||||
|
||||
// Used to toggle odd / even fields
|
||||
field_toggle = true;
|
||||
|
||||
@@ -60,11 +63,11 @@ FrameMapper::~FrameMapper() {
|
||||
/// Get the current reader
|
||||
ReaderBase* FrameMapper::Reader()
|
||||
{
|
||||
if (reader)
|
||||
return reader;
|
||||
else
|
||||
// Throw error if reader not initialized
|
||||
throw ReaderClosed("No Reader has been initialized for FrameMapper. Call Reader(*reader) before calling this method.");
|
||||
if (reader)
|
||||
return reader;
|
||||
else
|
||||
// Throw error if reader not initialized
|
||||
throw ReaderClosed("No Reader has been initialized for FrameMapper. Call Reader(*reader) before calling this method.");
|
||||
}
|
||||
|
||||
void FrameMapper::AddField(int64_t frame)
|
||||
@@ -84,14 +87,14 @@ void FrameMapper::AddField(Field field)
|
||||
|
||||
// Clear both the fields & frames lists
|
||||
void FrameMapper::Clear() {
|
||||
// Prevent async calls to the following code
|
||||
const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
|
||||
// Prevent async calls to the following code
|
||||
const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
|
||||
|
||||
// Clear the fields & frames lists
|
||||
fields.clear();
|
||||
fields.shrink_to_fit();
|
||||
frames.clear();
|
||||
frames.shrink_to_fit();
|
||||
// Clear the fields & frames lists
|
||||
fields.clear();
|
||||
fields.shrink_to_fit();
|
||||
frames.clear();
|
||||
frames.shrink_to_fit();
|
||||
}
|
||||
|
||||
// Use the original and target frame rates and a pull-down technique to create
|
||||
@@ -114,14 +117,14 @@ void FrameMapper::Init()
|
||||
Clear();
|
||||
|
||||
// Find parent position (if any)
|
||||
Clip *parent = (Clip *) ParentClip();
|
||||
if (parent) {
|
||||
parent_position = parent->Position();
|
||||
parent_start = parent->Start();
|
||||
} else {
|
||||
parent_position = 0.0;
|
||||
parent_start = 0.0;
|
||||
}
|
||||
Clip *parent = (Clip *) ParentClip();
|
||||
if (parent) {
|
||||
parent_position = parent->Position();
|
||||
parent_start = parent->Start();
|
||||
} else {
|
||||
parent_position = 0.0;
|
||||
parent_start = 0.0;
|
||||
}
|
||||
|
||||
// Mark as not dirty
|
||||
is_dirty = false;
|
||||
@@ -419,19 +422,19 @@ std::shared_ptr<Frame> FrameMapper::GetFrame(int64_t requested_frame)
|
||||
// Create a scoped lock, allowing only a single thread to run the following code at one time
|
||||
const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
|
||||
|
||||
// Find parent properties (if any)
|
||||
Clip *parent = (Clip *) ParentClip();
|
||||
if (parent) {
|
||||
float position = parent->Position();
|
||||
float start = parent->Start();
|
||||
if (parent_position != position || parent_start != start) {
|
||||
// Force dirty if parent clip has moved or been trimmed
|
||||
// since this heavily affects frame #s and audio mappings
|
||||
is_dirty = true;
|
||||
}
|
||||
}
|
||||
// Find parent properties (if any)
|
||||
Clip *parent = (Clip *) ParentClip();
|
||||
if (parent) {
|
||||
float position = parent->Position();
|
||||
float start = parent->Start();
|
||||
if (parent_position != position || parent_start != start) {
|
||||
// Force dirty if parent clip has moved or been trimmed
|
||||
// since this heavily affects frame #s and audio mappings
|
||||
is_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if mappings are dirty (and need to be recalculated)
|
||||
// Check if mappings are dirty (and need to be recalculated)
|
||||
if (is_dirty)
|
||||
Init();
|
||||
|
||||
@@ -513,9 +516,12 @@ std::shared_ptr<Frame> FrameMapper::GetFrame(int64_t requested_frame)
|
||||
std::make_shared<QImage>(*even_frame->GetImage()), false);
|
||||
}
|
||||
|
||||
// Determine if reader contains audio samples
|
||||
bool reader_has_audio = frame->SampleRate() > 0 && frame->GetAudioChannelsCount() > 0;
|
||||
|
||||
// Resample audio on frame (if needed)
|
||||
bool need_resampling = false;
|
||||
if (info.has_audio &&
|
||||
if ((info.has_audio && reader_has_audio) &&
|
||||
(info.sample_rate != frame->SampleRate() ||
|
||||
info.channels != frame->GetAudioChannelsCount() ||
|
||||
info.channel_layout != frame->ChannelsLayout()))
|
||||
@@ -537,7 +543,7 @@ std::shared_ptr<Frame> FrameMapper::GetFrame(int64_t requested_frame)
|
||||
copy_samples.sample_end += EXTRA_INPUT_SAMPLES;
|
||||
int samples_per_end_frame =
|
||||
Frame::GetSamplesPerFrame(copy_samples.frame_end, original,
|
||||
reader->info.sample_rate, reader->info.channels);
|
||||
reader->info.sample_rate, reader->info.channels);
|
||||
if (copy_samples.sample_end >= samples_per_end_frame)
|
||||
{
|
||||
// check for wrapping
|
||||
@@ -553,7 +559,7 @@ std::shared_ptr<Frame> FrameMapper::GetFrame(int64_t requested_frame)
|
||||
copy_samples.sample_start += EXTRA_INPUT_SAMPLES;
|
||||
int samples_per_start_frame =
|
||||
Frame::GetSamplesPerFrame(copy_samples.frame_start, original,
|
||||
reader->info.sample_rate, reader->info.channels);
|
||||
reader->info.sample_rate, reader->info.channels);
|
||||
if (copy_samples.sample_start >= samples_per_start_frame)
|
||||
{
|
||||
// check for wrapping
|
||||
@@ -643,15 +649,15 @@ void FrameMapper::PrintMapping(std::ostream* out)
|
||||
{
|
||||
MappedFrame frame = frames[map - 1];
|
||||
*out << "Target frame #: " << map
|
||||
<< " mapped to original frame #:\t("
|
||||
<< frame.Odd.Frame << " odd, "
|
||||
<< frame.Even.Frame << " even)" << std::endl;
|
||||
<< " mapped to original frame #:\t("
|
||||
<< frame.Odd.Frame << " odd, "
|
||||
<< frame.Even.Frame << " even)" << std::endl;
|
||||
|
||||
*out << " - Audio samples mapped to frame "
|
||||
<< frame.Samples.frame_start << ":"
|
||||
<< frame.Samples.sample_start << " to frame "
|
||||
<< frame.Samples.frame_end << ":"
|
||||
<< frame.Samples.sample_end << endl;
|
||||
<< frame.Samples.frame_start << ":"
|
||||
<< frame.Samples.sample_start << " to frame "
|
||||
<< frame.Samples.frame_end << ":"
|
||||
<< frame.Samples.sample_end << endl;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -690,21 +696,21 @@ void FrameMapper::Close()
|
||||
reader->Close();
|
||||
}
|
||||
|
||||
// Clear the fields & frames lists
|
||||
Clear();
|
||||
// Clear the fields & frames lists
|
||||
Clear();
|
||||
|
||||
// Mark as dirty
|
||||
is_dirty = true;
|
||||
// Mark as dirty
|
||||
is_dirty = true;
|
||||
|
||||
// Clear cache
|
||||
final_cache.Clear();
|
||||
// Clear cache
|
||||
final_cache.Clear();
|
||||
|
||||
// Deallocate resample buffer
|
||||
if (avr) {
|
||||
SWR_CLOSE(avr);
|
||||
SWR_FREE(&avr);
|
||||
avr = NULL;
|
||||
}
|
||||
// Deallocate resample buffer
|
||||
if (avr) {
|
||||
SWR_CLOSE(avr);
|
||||
SWR_FREE(&avr);
|
||||
avr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -785,6 +791,9 @@ void FrameMapper::ChangeMapping(Fraction target_fps, PulldownType target_pulldow
|
||||
info.channels = target_channels;
|
||||
info.channel_layout = target_channel_layout;
|
||||
|
||||
// Enable/Disable audio (based on settings)
|
||||
info.has_audio = info.sample_rate > 0 && info.channels > 0;
|
||||
|
||||
// Clear cache
|
||||
final_cache.Clear();
|
||||
|
||||
@@ -913,28 +922,28 @@ void FrameMapper::ResampleMappedAudio(std::shared_ptr<Frame> frame, int64_t orig
|
||||
|
||||
int nb_samples = 0;
|
||||
|
||||
// setup resample context
|
||||
if (!avr) {
|
||||
avr = SWR_ALLOC();
|
||||
av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0);
|
||||
av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0);
|
||||
av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
||||
av_opt_set_int(avr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
||||
av_opt_set_int(avr, "in_sample_rate", sample_rate_in_frame, 0);
|
||||
av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0);
|
||||
av_opt_set_int(avr, "in_channels", channels_in_frame, 0);
|
||||
av_opt_set_int(avr, "out_channels", info.channels, 0);
|
||||
SWR_INIT(avr);
|
||||
}
|
||||
// setup resample context
|
||||
if (!avr) {
|
||||
avr = SWR_ALLOC();
|
||||
av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0);
|
||||
av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0);
|
||||
av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
||||
av_opt_set_int(avr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
||||
av_opt_set_int(avr, "in_sample_rate", sample_rate_in_frame, 0);
|
||||
av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0);
|
||||
av_opt_set_int(avr, "in_channels", channels_in_frame, 0);
|
||||
av_opt_set_int(avr, "out_channels", info.channels, 0);
|
||||
SWR_INIT(avr);
|
||||
}
|
||||
|
||||
// Convert audio samples
|
||||
nb_samples = SWR_CONVERT(avr, // audio resample context
|
||||
audio_converted->data, // output data pointers
|
||||
audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown)
|
||||
audio_converted->nb_samples, // maximum number of samples that the output buffer can hold
|
||||
audio_frame->data, // input data pointers
|
||||
audio_frame->linesize[0], // input plane size, in bytes (0 if unknown)
|
||||
audio_frame->nb_samples); // number of input samples to convert
|
||||
// Convert audio samples
|
||||
nb_samples = SWR_CONVERT(avr, // audio resample context
|
||||
audio_converted->data, // output data pointers
|
||||
audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown)
|
||||
audio_converted->nb_samples, // maximum number of samples that the output buffer can hold
|
||||
audio_frame->data, // input data pointers
|
||||
audio_frame->linesize[0], // input plane size, in bytes (0 if unknown)
|
||||
audio_frame->nb_samples); // number of input samples to convert
|
||||
|
||||
// Create a new array (to hold all resampled S16 audio samples)
|
||||
int16_t* resampled_samples = new int16_t[(nb_samples * info.channels)];
|
||||
|
||||
112
src/Profiles.cpp
112
src/Profiles.cpp
@@ -10,11 +10,27 @@
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#include <iomanip>
|
||||
#include "Profiles.h"
|
||||
#include "Exceptions.h"
|
||||
|
||||
using namespace openshot;
|
||||
|
||||
// default constructor
|
||||
Profile::Profile() {
|
||||
// Initialize info values
|
||||
info.description = "";
|
||||
info.height = 0;
|
||||
info.width = 0;
|
||||
info.pixel_format = 0;
|
||||
info.fps.num = 0;
|
||||
info.fps.den = 0;
|
||||
info.pixel_ratio.num = 0;
|
||||
info.pixel_ratio.den = 0;
|
||||
info.display_ratio.num = 0;
|
||||
info.display_ratio.den = 0;
|
||||
info.interlaced_frame = false;
|
||||
}
|
||||
|
||||
// @brief Constructor for Profile.
|
||||
// @param path The folder path / location of a profile file
|
||||
@@ -22,21 +38,11 @@ Profile::Profile(std::string path) {
|
||||
|
||||
bool read_file = false;
|
||||
|
||||
// Call default constructor
|
||||
Profile();
|
||||
|
||||
try
|
||||
{
|
||||
// Initialize info values
|
||||
info.description = "";
|
||||
info.height = 0;
|
||||
info.width = 0;
|
||||
info.pixel_format = 0;
|
||||
info.fps.num = 0;
|
||||
info.fps.den = 0;
|
||||
info.pixel_ratio.num = 0;
|
||||
info.pixel_ratio.den = 0;
|
||||
info.display_ratio.num = 0;
|
||||
info.display_ratio.den = 0;
|
||||
info.interlaced_frame = false;
|
||||
|
||||
QFile inputFile(path.c_str());
|
||||
if (inputFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
@@ -55,8 +61,9 @@ Profile::Profile(std::string path) {
|
||||
int value_int = 0;
|
||||
|
||||
// update struct (based on line number)
|
||||
if (setting == "description")
|
||||
if (setting == "description") {
|
||||
info.description = value;
|
||||
}
|
||||
else if (setting == "frame_rate_num") {
|
||||
std::stringstream(value) >> value_int;
|
||||
info.fps.num = value_int;
|
||||
@@ -98,7 +105,7 @@ Profile::Profile(std::string path) {
|
||||
info.pixel_format = value_int;
|
||||
}
|
||||
}
|
||||
read_file = true;
|
||||
read_file = true;
|
||||
inputFile.close();
|
||||
}
|
||||
|
||||
@@ -115,6 +122,81 @@ Profile::Profile(std::string path) {
|
||||
throw InvalidFile("Profile could not be found or loaded (or is invalid).", path);
|
||||
}
|
||||
|
||||
// Return a formatted FPS
|
||||
std::string Profile::formattedFPS(bool include_decimal) {
|
||||
// Format FPS to use 2 decimals (if needed)
|
||||
float fps = info.fps.ToFloat();
|
||||
std::stringstream fps_string;
|
||||
if (info.fps.den == 1) {
|
||||
// For example: 24.0 will become 24
|
||||
fps_string << std::fixed << std::setprecision(0) << fps;
|
||||
} else {
|
||||
// For example: 29.97002997 will become 29.97
|
||||
fps_string << std::fixed << std::setprecision(2) << fps;
|
||||
// Remove decimal place using QString (for convenience)
|
||||
if (!include_decimal) {
|
||||
QString fps_qstring = QString::fromStdString(fps_string.str());
|
||||
fps_qstring.replace(".", "");
|
||||
fps_string.str(fps_qstring.toStdString());
|
||||
}
|
||||
}
|
||||
return fps_string.str();
|
||||
}
|
||||
|
||||
// Return a unique key of this profile (01920x1080i2997_16-09)
|
||||
std::string Profile::Key() {
|
||||
std::stringstream output;
|
||||
std::string progressive_str = "p";
|
||||
if (info.interlaced_frame) {
|
||||
progressive_str = "i";
|
||||
}
|
||||
std::string fps_string = formattedFPS(false);
|
||||
output << std::setfill('0') << std::setw(5) << info.width << std::setfill('\0') << "x";
|
||||
output << std::setfill('0') << std::setw(4) << info.height << std::setfill('\0') << progressive_str;
|
||||
output << std::setfill('0') << std::setw(4) << fps_string << std::setfill('\0') << "_";
|
||||
output << std::setfill('0') << std::setw(2) << info.display_ratio.num << std::setfill('\0') << "-";
|
||||
output << std::setfill('0') << std::setw(2) << info.display_ratio.den << std::setfill('\0');
|
||||
return output.str();
|
||||
}
|
||||
|
||||
// Return the name of this profile (1920x1080p29.97)
|
||||
std::string Profile::ShortName() {
|
||||
std::stringstream output;
|
||||
std::string progressive_str = "p";
|
||||
if (info.interlaced_frame) {
|
||||
progressive_str = "i";
|
||||
}
|
||||
std::string fps_string = formattedFPS(true);
|
||||
output << info.width << "x" << info.height << progressive_str << fps_string;
|
||||
return output.str();
|
||||
}
|
||||
|
||||
// Return a longer format name (1920x1080p @ 29.97 fps (16:9))
|
||||
std::string Profile::LongName() {
|
||||
std::stringstream output;
|
||||
std::string progressive_str = "p";
|
||||
if (info.interlaced_frame) {
|
||||
progressive_str = "i";
|
||||
}
|
||||
std::string fps_string = formattedFPS(true);
|
||||
output << info.width << "x" << info.height << progressive_str << " @ " << fps_string
|
||||
<< " fps (" << info.display_ratio.num << ":" << info.display_ratio.den << ")";
|
||||
return output.str();
|
||||
}
|
||||
|
||||
// Return a longer format name (1920x1080p @ 29.97 fps (16:9) HD 1080i 29.97 fps)
|
||||
std::string Profile::LongNameWithDesc() {
|
||||
std::stringstream output;
|
||||
std::string progressive_str = "p";
|
||||
if (info.interlaced_frame) {
|
||||
progressive_str = "i";
|
||||
}
|
||||
std::string fps_string = formattedFPS(true);
|
||||
output << info.width << "x" << info.height << progressive_str << " @ " << fps_string
|
||||
<< " fps (" << info.display_ratio.num << ":" << info.display_ratio.den << ") " << info.description;
|
||||
return output.str();
|
||||
}
|
||||
|
||||
// Generate JSON string of this object
|
||||
std::string Profile::Json() const {
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <math.h>
|
||||
#include <fstream>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
@@ -62,14 +63,90 @@ namespace openshot
|
||||
*/
|
||||
class Profile
|
||||
{
|
||||
private:
|
||||
std::string formattedFPS(bool include_decimal); ///< Return a formatted FPS
|
||||
|
||||
/// Less than operator (compare profile objects)
|
||||
/// Compare # of pixels, then FPS, then DAR
|
||||
friend bool operator<(const Profile& l, const Profile& r)
|
||||
{
|
||||
double left_fps = l.info.fps.ToDouble();
|
||||
double right_fps = r.info.fps.ToDouble();
|
||||
double left_pixels = l.info.width * l.info.height;
|
||||
double right_pixels = r.info.width * r.info.height;
|
||||
double left_dar = l.info.display_ratio.ToDouble();
|
||||
double right_dar = r.info.display_ratio.ToDouble();
|
||||
|
||||
if (left_pixels < right_pixels) {
|
||||
// less pixels
|
||||
return true;
|
||||
} else {
|
||||
if (left_fps < right_fps) {
|
||||
// less FPS
|
||||
return true;
|
||||
} else {
|
||||
if (left_dar < right_dar) {
|
||||
// less DAR
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Greater than operator (compare profile objects)
|
||||
/// Compare # of pixels, then FPS, then DAR
|
||||
friend bool operator>(const Profile& l, const Profile& r)
|
||||
{
|
||||
double left_fps = l.info.fps.ToDouble();
|
||||
double right_fps = r.info.fps.ToDouble();
|
||||
double left_pixels = l.info.width * l.info.height;
|
||||
double right_pixels = r.info.width * r.info.height;
|
||||
double left_dar = l.info.display_ratio.ToDouble();
|
||||
double right_dar = r.info.display_ratio.ToDouble();
|
||||
|
||||
if (left_pixels > right_pixels) {
|
||||
// less pixels
|
||||
return true;
|
||||
} else {
|
||||
if (left_fps > right_fps) {
|
||||
// less FPS
|
||||
return true;
|
||||
} else {
|
||||
if (left_dar > right_dar) {
|
||||
// less DAR
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Equality operator (compare profile objects)
|
||||
friend bool operator==(const Profile& l, const Profile& r)
|
||||
{
|
||||
return std::tie(l.info.width, l.info.height, l.info.fps.num, l.info.fps.den, l.info.display_ratio.num, l.info.display_ratio.den, l.info.interlaced_frame)
|
||||
== std::tie(r.info.width, r.info.height, r.info.fps.num, r.info.fps.den, r.info.display_ratio.num, r.info.display_ratio.den, r.info.interlaced_frame);
|
||||
}
|
||||
|
||||
public:
|
||||
/// Profile data stored here
|
||||
ProfileInfo info;
|
||||
|
||||
/// @brief Default Constructor for Profile.
|
||||
Profile();
|
||||
|
||||
/// @brief Constructor for Profile.
|
||||
/// @param path The folder path / location of a profile file
|
||||
Profile(std::string path);
|
||||
|
||||
std::string Key(); ///< Return a unique key of this profile with padding (01920x1080i2997_16:09)
|
||||
std::string ShortName(); ///< Return the name of this profile (1920x1080p29.97)
|
||||
std::string LongName(); ///< Return a longer format name (1920x1080p @ 29.97 fps (16:9))
|
||||
std::string LongNameWithDesc(); ///< Return a longer format name with description (1920x1080p @ 29.97 fps (16:9) HD 1080i 29.97 fps)
|
||||
|
||||
// Get and Set JSON methods
|
||||
std::string Json() const; ///< Generate JSON string of this object
|
||||
Json::Value JsonValue() const; ///< Generate Json::Value for this object
|
||||
|
||||
@@ -35,6 +35,7 @@ set(OPENSHOT_TESTS
|
||||
FrameMapper
|
||||
KeyFrame
|
||||
Point
|
||||
Profiles
|
||||
QtImageReader
|
||||
ReaderBase
|
||||
Settings
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "FFmpegReader.h"
|
||||
#include "Fraction.h"
|
||||
#include "Frame.h"
|
||||
#include "Timeline.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace openshot;
|
||||
@@ -183,3 +184,52 @@ TEST_CASE( "DisplayInfo", "[libopenshot][ffmpegwriter]" )
|
||||
// Compare a [0, expected.size()) substring of output to expected
|
||||
CHECK(output.str().substr(0, expected.size()) == expected);
|
||||
}
|
||||
|
||||
TEST_CASE( "Gif", "[libopenshot][ffmpegwriter]" )
|
||||
{
|
||||
// Reader
|
||||
std::stringstream path;
|
||||
path << TEST_MEDIA_PATH << "sintel_trailer-720p.mp4";
|
||||
|
||||
// Create Gif Clip
|
||||
Clip clip_video(path.str());
|
||||
clip_video.Layer(0);
|
||||
clip_video.Position(0.0);
|
||||
clip_video.Open();
|
||||
|
||||
// Create Timeline w/ 1 Gif Clip (with 0 sample rate, and 0 channels)
|
||||
openshot::Timeline t(1280, 720, Fraction(30,1), 0, 0, LAYOUT_MONO);
|
||||
t.AddClip(&clip_video);
|
||||
t.Open();
|
||||
|
||||
/* WRITER ---------------- */
|
||||
FFmpegWriter w("output1.gif");
|
||||
|
||||
// Set options (no audio options are set)
|
||||
w.SetVideoOptions(true, "gif", Fraction(24,1), 1280, 720, Fraction(1,1), false, false, 15000000);
|
||||
|
||||
// Create streams
|
||||
w.PrepareStreams();
|
||||
|
||||
// Open writer
|
||||
w.Open();
|
||||
|
||||
// Write some frames
|
||||
w.WriteFrame(&t, 1, 60);
|
||||
|
||||
// Close writer & reader
|
||||
w.Close();
|
||||
t.Close();
|
||||
|
||||
FFmpegReader r1("output1.gif");
|
||||
r1.Open();
|
||||
|
||||
// Verify various settings on new Gif
|
||||
CHECK(r1.GetFrame(1)->GetAudioChannelsCount() == 0);
|
||||
CHECK(r1.GetFrame(1)->GetAudioSamplesCount() == 0);
|
||||
CHECK(r1.info.fps.num == 24);
|
||||
CHECK(r1.info.fps.den == 1);
|
||||
|
||||
// Close reader
|
||||
r1.Close();
|
||||
}
|
||||
|
||||
136
tests/Profiles.cpp
Normal file
136
tests/Profiles.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* @file
|
||||
* @brief Unit tests for openshot::Profile
|
||||
* @author Jonathan Thomas <jonathan@openshot.org>
|
||||
*
|
||||
* @ref License
|
||||
*/
|
||||
|
||||
// Copyright (c) 2008-2023 OpenShot Studios, LLC
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
#include "openshot_catch.h"
|
||||
|
||||
|
||||
#include "Profiles.h"
|
||||
|
||||
TEST_CASE( "empty constructor", "[libopenshot][profile]" )
|
||||
{
|
||||
openshot::Profile p1;
|
||||
|
||||
// Default values
|
||||
CHECK(p1.info.width == 0);
|
||||
CHECK(p1.info.height == 0);
|
||||
CHECK(p1.info.fps.num == 0);
|
||||
CHECK(p1.info.fps.den == 0);
|
||||
CHECK(p1.info.display_ratio.num == 0);
|
||||
CHECK(p1.info.display_ratio.den == 0);
|
||||
CHECK(p1.info.pixel_ratio.num == 0);
|
||||
CHECK(p1.info.pixel_ratio.den == 0);
|
||||
CHECK(p1.info.interlaced_frame == false);
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE( "constructor with example profiles", "[libopenshot][profile]" )
|
||||
{
|
||||
std::stringstream profile1;
|
||||
profile1 << TEST_MEDIA_PATH << "example_profile1";
|
||||
|
||||
openshot::Profile p1(profile1.str());
|
||||
|
||||
// Default values
|
||||
CHECK(p1.info.width == 1280);
|
||||
CHECK(p1.info.height == 720);
|
||||
CHECK(p1.info.fps.num == 24);
|
||||
CHECK(p1.info.fps.den == 1);
|
||||
CHECK(p1.info.display_ratio.num == 16);
|
||||
CHECK(p1.info.display_ratio.den == 9);
|
||||
CHECK(p1.info.pixel_ratio.num == 1);
|
||||
CHECK(p1.info.pixel_ratio.den == 1);
|
||||
CHECK(p1.info.interlaced_frame == false);
|
||||
|
||||
std::stringstream profile2;
|
||||
profile2 << TEST_MEDIA_PATH << "example_profile2";
|
||||
|
||||
openshot::Profile p2(profile2.str());
|
||||
|
||||
// Default values
|
||||
CHECK(p2.info.width == 1920);
|
||||
CHECK(p2.info.height == 1080);
|
||||
CHECK(p2.info.fps.num == 30000);
|
||||
CHECK(p2.info.fps.den == 1001);
|
||||
CHECK(p2.info.display_ratio.num == 16);
|
||||
CHECK(p2.info.display_ratio.den == 9);
|
||||
CHECK(p2.info.pixel_ratio.num == 1);
|
||||
CHECK(p2.info.pixel_ratio.den == 1);
|
||||
CHECK(p2.info.interlaced_frame == true);
|
||||
}
|
||||
|
||||
TEST_CASE( "24 fps names", "[libopenshot][profile]" )
|
||||
{
|
||||
std::stringstream path;
|
||||
path << TEST_MEDIA_PATH << "example_profile1";
|
||||
|
||||
openshot::Profile p(path.str());
|
||||
|
||||
// Default values
|
||||
CHECK(p.Key() == "01280x0720p0024_16-09");
|
||||
CHECK(p.ShortName() == "1280x720p24");
|
||||
CHECK(p.LongName() == "1280x720p @ 24 fps (16:9)");
|
||||
CHECK(p.LongNameWithDesc() == "1280x720p @ 24 fps (16:9) HD 720p 24 fps");
|
||||
}
|
||||
|
||||
TEST_CASE( "29.97 fps names", "[libopenshot][profile]" )
|
||||
{
|
||||
std::stringstream path;
|
||||
path << TEST_MEDIA_PATH << "example_profile2";
|
||||
|
||||
openshot::Profile p(path.str());
|
||||
|
||||
// Default values
|
||||
CHECK(p.Key() == "01920x1080i2997_16-09");
|
||||
CHECK(p.ShortName() == "1920x1080i29.97");
|
||||
CHECK(p.LongName() == "1920x1080i @ 29.97 fps (16:9)");
|
||||
CHECK(p.LongNameWithDesc() == "1920x1080i @ 29.97 fps (16:9) HD 1080i 29.97 fps");
|
||||
}
|
||||
|
||||
TEST_CASE( "compare profiles", "[libopenshot][profile]" )
|
||||
{
|
||||
// 720p24
|
||||
std::stringstream profile1;
|
||||
profile1 << TEST_MEDIA_PATH << "example_profile1";
|
||||
openshot::Profile p1(profile1.str());
|
||||
|
||||
// 720p24 (copy)
|
||||
openshot::Profile p1copy(profile1.str());
|
||||
|
||||
// 1080i2997
|
||||
std::stringstream profile2;
|
||||
profile2 << TEST_MEDIA_PATH << "example_profile2";
|
||||
openshot::Profile p2(profile2.str());
|
||||
|
||||
// 1080i2997 (copy)
|
||||
openshot::Profile p2copy(profile2.str());
|
||||
|
||||
CHECK(p1 < p2);
|
||||
CHECK(p2 > p1);
|
||||
CHECK(p1 == p1copy);
|
||||
CHECK(p2 == p2copy);
|
||||
|
||||
// 720p60
|
||||
openshot::Profile p3(profile1.str());
|
||||
p3.info.fps.num = 60;
|
||||
|
||||
CHECK(p1 < p3);
|
||||
CHECK_FALSE(p1 == p3);
|
||||
|
||||
// 72024, DAR: 4:3
|
||||
p3.info.fps.num = 24;
|
||||
p3.info.display_ratio.num = 4;
|
||||
p3.info.display_ratio.den = 3;
|
||||
|
||||
CHECK(p1 > p3);
|
||||
CHECK(p3 < p1);
|
||||
CHECK_FALSE(p1 == p3);
|
||||
}
|
||||
Reference in New Issue
Block a user