OpenShot Library | libopenshot 1.1.1
QtTextReader.cpp
Go to the documentation of this file.
1
11// Copyright (c) 2008-2019 OpenShot Studios, LLC
12//
13// SPDX-License-Identifier: LGPL-3.0-or-later
14
15#include "QtTextReader.h"
16#include "CacheBase.h"
17#include "Clip.h"
18#include "Timeline.h"
19#include "Exceptions.h"
20#include "Frame.h"
21#include "Color.h"
22
23#include <QImage>
24#include <QPainter>
25#include <QTextDocument>
26#include <QTextCursor>
27#include <QAbstractTextDocumentLayout>
28#include <algorithm>
29#include <cmath>
30
31using namespace openshot;
32
34QtTextReader::QtTextReader() : width(1024), height(768), x_offset(0), y_offset(0), text(""), font(QFont("Arial", 10)), text_color("#ffffff"), background_color("#000000"), is_open(false), gravity(GRAVITY_CENTER)
35{
36 // Populate attributes (such as height, width, etc...) without rendering
37 UpdateInfo();
38}
39
40QtTextReader::QtTextReader(int width, int height, int x_offset, int y_offset, GravityType gravity, std::string text, QFont font, std::string text_color, std::string background_color)
41: width(width), height(height), x_offset(x_offset), y_offset(y_offset), text(text), font(font), text_color(text_color), background_color(background_color), is_open(false), gravity(gravity)
42{
43 // Populate attributes (such as height, width, etc...) without rendering
44 UpdateInfo();
45}
46
47void QtTextReader::SetTextBackgroundColor(std::string color) {
48 text_background_color = color;
49
50 // Re-render an open reader with the new text background color
51 if (is_open) {
52 Close();
53 Open();
54 }
55}
56
57// Clip-local style curves are evaluated in source frames, like other readers.
58Json::Value QtTextReader::EvaluateStyle(int64_t frame) const {
59 Json::Value result(Json::objectValue);
60 for (const auto& key : text_animation.getMemberNames()) {
61 if (key == "color" || key == "background" || key == "stroke" || key == "border") {
62 Color color("#ffffff");
63 color.SetJsonValue(text_animation[key]);
64 auto rgba = color.GetColorRGBA(frame);
65 result[key] = QColor(rgba[0], rgba[1], rgba[2], rgba[3]).name(QColor::HexArgb).toStdString();
66 } else if (key == "stroke_width" || key == "border_width" || key == "background_corner") {
67 Keyframe value;
68 value.SetJsonValue(text_animation[key]);
69 result[key] = std::max(0.0, value.GetValue(frame));
70 }
71 }
72 return result;
73}
74
75// Raster scale that keeps this text sharp at the parent clip's largest size,
76// like QtImageReader does for SVGs. SCALE_NONE draws a source at its pixel
77// size, so it also follows the preview-to-project ratio, and `detail` is the
78// extra scale that Clip shrinks back out (see DetailScale).
79double QtTextReader::RasterScale(double& detail) {
80 detail = 1.0;
81 Clip* parent = (Clip*) ParentClip();
82 if (!parent || !parent->ParentTimeline())
83 return 1.0;
84 Timeline* timeline = (Timeline*) parent->ParentTimeline();
85 if (timeline->preview_width <= 0 || timeline->preview_height <= 0 || timeline->info.width <= 0)
86 return 1.0;
87 const double clip_scale = std::max({1.0, double(parent->scale_x.GetMaxPoint().co.Y),
88 double(parent->scale_y.GetMaxPoint().co.Y)});
89 // Bound the raster (a 16x scale of a full-frame box would need gigabytes)
90 const double max_scale = std::max(1.0, 8192.0 / std::max(width, height));
91 if (parent->scale == SCALE_NONE) {
92 const double preview = timeline->preview_width / double(timeline->info.width);
93 detail = std::max(1.0, std::min(clip_scale, max_scale / preview));
94 return preview * detail;
95 }
96 // Other modes resize the image to the preview, so only its pixel count matters
97 QSizeF size(width, height);
98 size.scale(timeline->preview_width, timeline->preview_height,
99 parent->scale == SCALE_CROP ? Qt::KeepAspectRatioByExpanding :
100 parent->scale == SCALE_STRETCH ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio);
101 const double fit = std::max(size.width() / width, size.height() / height);
102 return std::min(fit * clip_scale, max_scale);
103}
104
105// Render one style into a new image (layout in project pixels, raster at render_scale)
106std::shared_ptr<QImage> QtTextReader::Render(const Json::Value& style)
107{
108 const auto color_at = [&](const char* key, const std::string& fallback) {
109 return style.isMember(key) ? style[key].asString() : fallback;
110 };
111 const auto number_at = [&](const char* key, double fallback) {
112 return style.isMember(key) ? style[key].asDouble() : fallback;
113 };
114 const auto text_color = color_at("color", this->text_color);
115 const auto background_color = color_at("background", this->background_color);
116 const auto stroke = color_at("stroke", this->stroke);
117 const auto border = color_at("border", this->border);
118 const auto stroke_width = number_at("stroke_width", this->stroke_width);
119 const auto border_width = number_at("border_width", this->border_width);
120 // A radius past half the box would only distort the curve.
121 const double corner = std::min(std::max(0.0, number_at("background_corner", this->background_corner)),
122 std::min(width, height) / 2.0);
123
124 // create image (layout stays in project pixels; only the raster is scaled)
125 const int image_width = std::max(1, int(std::round(width * render_scale)));
126 const int image_height = std::max(1, int(std::round(height * render_scale)));
127 auto image = std::make_shared<QImage>(image_width, image_height, QImage::Format_RGBA8888_Premultiplied);
128 image->fill(corner > 0 ? QColor(Qt::transparent) : QColor(background_color.c_str()));
129
130 QPainter painter;
131 if (!painter.begin(image.get())) {
132 return nullptr;
133 }
134 painter.scale(image_width / double(width), image_height / double(height));
135 if (corner > 0) {
136 painter.save();
137 painter.setRenderHint(QPainter::Antialiasing);
138 painter.setPen(Qt::NoPen);
139 painter.setBrush(QColor(background_color.c_str()));
140 painter.drawRoundedRect(QRectF(0, 0, width, height), corner, corner);
141 painter.restore();
142 }
143
144 // set background
145 if (!text_background_color.empty()) {
146 painter.setBackgroundMode(Qt::OpaqueMode);
147 painter.setBackground(QBrush(text_background_color.c_str()));
148 }
149
150 // set font color
151 painter.setPen(QPen(text_color.c_str()));
152
153 // set font
154 QFont text_font(font);
155 if (letter_spacing != 0.0)
156 text_font.setLetterSpacing(QFont::AbsoluteSpacing, letter_spacing);
157 painter.setFont(text_font);
158
159 // Set gravity (map between OpenShot and Qt)
160 int align_flag = 0;
161 switch (gravity)
162 {
163 case GRAVITY_TOP_LEFT:
164 align_flag = Qt::AlignLeft | Qt::AlignTop;
165 break;
166 case GRAVITY_TOP:
167 align_flag = Qt::AlignHCenter | Qt::AlignTop;
168 break;
170 align_flag = Qt::AlignRight | Qt::AlignTop;
171 break;
172 case GRAVITY_LEFT:
173 align_flag = Qt::AlignVCenter | Qt::AlignLeft;
174 break;
175 case GRAVITY_CENTER:
176 align_flag = Qt::AlignCenter;
177 break;
178 case GRAVITY_RIGHT:
179 align_flag = Qt::AlignVCenter | Qt::AlignRight;
180 break;
182 align_flag = Qt::AlignLeft | Qt::AlignBottom;
183 break;
184 case GRAVITY_BOTTOM:
185 align_flag = Qt::AlignHCenter | Qt::AlignBottom;
186 break;
188 align_flag = Qt::AlignRight | Qt::AlignBottom;
189 break;
190 }
191
192 // Draw image
193 if (document_layout) {
194 QTextDocument document;
195 document.setDefaultFont(text_font);
196 document.setDocumentMargin(background_padding);
197 QTextOption option;
198 option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
199 option.setAlignment(Qt::Alignment(align_flag & (Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter)));
200 document.setDefaultTextOption(option);
201 document.setPlainText(QString::fromStdString(text));
202 document.setTextWidth(width);
203 QTextCursor cursor(&document);
204 cursor.select(QTextCursor::Document);
205 if (line_spacing != 1.0) {
206 QTextBlockFormat block;
207 block.setLineHeight(line_spacing * 100.0, QTextBlockFormat::ProportionalHeight);
208 cursor.mergeBlockFormat(block);
209 }
210 QAbstractTextDocumentLayout::PaintContext context;
211 context.palette.setColor(QPalette::Text, QColor(QString::fromStdString(text_color)));
212 painter.save();
213 painter.translate(x_offset, y_offset);
214 QTextCharFormat format;
215 if (stroke_width > 0) {
216 // Outline first, then the fill on top, so a wide outline
217 // grows outward instead of covering the letters (as Caption).
218 format.setForeground(Qt::transparent);
219 format.setTextOutline(QPen(QColor(QString::fromStdString(stroke)), stroke_width));
220 cursor.mergeCharFormat(format);
221 document.documentLayout()->draw(&painter, context);
222 format.setTextOutline(QPen(Qt::transparent, 0)); // Fill as a path, like the outline.
223 }
224 format.setForeground(QColor(QString::fromStdString(text_color)));
225 cursor.mergeCharFormat(format);
226 document.documentLayout()->draw(&painter, context);
227 painter.restore();
228 if (border_width > 0) {
229 // Square corners at any width (the default bevel cuts them off).
230 QPen border_pen(QColor(QString::fromStdString(border)), border_width);
231 border_pen.setJoinStyle(Qt::MiterJoin);
232 painter.setPen(border_pen);
233 painter.setBrush(Qt::NoBrush);
234 const QRectF border_rect(border_width / 2, border_width / 2,
235 width - border_width, height - border_width);
236 if (corner > 0) {
237 // The border's outer edge follows the background's curve.
238 const double radius = std::max(0.0, corner - border_width / 2);
239 painter.setRenderHint(QPainter::Antialiasing);
240 painter.drawRoundedRect(border_rect, radius, radius);
241 } else {
242 painter.drawRect(border_rect);
243 }
244 }
245 } else {
246 painter.drawText(x_offset, y_offset, width, height, align_flag, text.c_str());
247 }
248
249 painter.end();
250
251 return image;
252}
253
254// Populate reader info from the current properties
255void QtTextReader::UpdateInfo()
256{
257 info.has_audio = false;
258 info.has_video = true;
259 info.has_single_image = text_animation.empty();
260 info.file_size = 0;
261 info.vcodec = "QImage";
262 info.width = width;
263 info.height = height;
264 info.pixel_ratio.num = 1;
265 info.pixel_ratio.den = 1;
266 info.duration = 60 * 60 * 1; // 1 hour duration
267 // Style curves use the project's frame numbers, so match its frame rate
268 // and let the timeline's FrameMapper pass frames through unchanged.
269 info.fps = source_fps;
270 info.video_timebase = source_fps.Reciprocal();
272
273 // Calculate the DAR (display aspect ratio)
275
276 // Reduce size fraction
277 font_size.Reduce();
278
279 // Set the ratio based on the reduced fraction
280 info.display_ratio.num = font_size.num;
281 info.display_ratio.den = font_size.den;
282}
283
284// Show a style, reusing a recent render of an identical style when possible
285void QtTextReader::UseStyle(const Json::Value& style)
286{
287 const std::string key = style.toStyledString();
288 for (auto entry = render_cache.begin(); entry != render_cache.end(); ++entry) {
289 if (entry->first == key) {
290 render_cache.splice(render_cache.begin(), render_cache, entry);
291 image = entry->second;
292 rendered_style = style;
293 return;
294 }
295 }
296 auto rendered = Render(style);
297 if (!rendered)
298 return;
299 image = rendered;
300 rendered_style = style;
301 render_cache.emplace_front(key, rendered);
302 // Out-of-order frames from parallel rendering must not re-render held
303 // styles, but cached rasters stay small: a few entries, 16 MB at most.
304 const qint64 max_bytes = 16 * 1024 * 1024;
305 qint64 bytes = 0;
306 size_t count = 0;
307 for (auto entry = render_cache.begin(); entry != render_cache.end();) {
308 bytes += entry->second->sizeInBytes();
309 if (++count > 4 || (count > 1 && bytes > max_bytes))
310 entry = render_cache.erase(entry);
311 else
312 ++entry;
313 }
314}
315
316// Open reader
318{
319 // Open reader if not already open
320 if (!is_open)
321 {
322 UseStyle(EvaluateStyle(render_frame));
323 if (!image)
324 return;
325
326 // Update image properties
327 UpdateInfo();
328
329 // Mark as "open"
330 is_open = true;
331 }
332}
333
334// Close reader
336{
337 // Close all objects, if reader is 'open'
338 if (is_open)
339 {
340 // Mark as "closed"
341 is_open = false;
342
343 // Delete the image and recent renders
344 image.reset();
345 render_cache.clear();
346
347 info.vcodec = "";
348 info.acodec = "";
349 }
350}
351
352// Get an openshot::Frame object for a specific frame number of this reader.
353std::shared_ptr<Frame> QtTextReader::GetFrame(int64_t requested_frame)
354{
355 // Create a scoped lock, allowing only a single thread to run the following code at one time
356 const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
357
358 auto sample_count = Frame::GetSamplesPerFrame(requested_frame, info.fps, info.sample_rate, info.channels);
359
360 double detail = 1.0;
361 const double raster_scale = RasterScale(detail);
362 if (std::abs(raster_scale - render_scale) > 1e-6 || std::abs(detail - detail_scale) > 1e-6) {
363 render_scale = raster_scale;
364 detail_scale = detail;
365 render_frame = requested_frame;
366 Close();
367 Open();
368 }
369 if (is_open && !text_animation.empty()) {
370 const auto style = EvaluateStyle(requested_frame);
371 if (style != rendered_style)
372 UseStyle(style);
373 }
374
375 if (image)
376 {
377 // Create or get frame object
378 auto image_frame = std::make_shared<Frame>(
379 requested_frame, image->size().width(), image->size().height(),
380 background_color, sample_count, info.channels);
381
382 // Add Image data to frame
383 image_frame->AddImage(image);
384
385 // return frame object
386 return image_frame;
387 } else {
388 // return empty frame
389 auto image_frame = std::make_shared<Frame>(1, 640, 480, background_color, sample_count, info.channels);
390
391 // return frame object
392 return image_frame;
393 }
394}
395
396// Generate JSON string of this object
397std::string QtTextReader::Json() const {
398
399 // Return formatted string
400 return JsonValue().toStyledString();
401}
402
403// Generate Json::Value for this object
404Json::Value QtTextReader::JsonValue() const {
405
406 // Create root json object
407 Json::Value root = ReaderBase::JsonValue(); // get parent properties
408 root["type"] = "QtTextReader";
409 root["document_layout"] = document_layout;
410 root["text_animation"] = text_animation;
411 root["background_padding"] = background_padding;
412 root["stroke_width"] = stroke_width;
413 root["stroke"] = stroke;
414 root["border_width"] = border_width;
415 root["border"] = border;
416 root["letter_spacing"] = letter_spacing;
417 root["line_spacing"] = line_spacing;
418 root["background_corner"] = background_corner;
419 root["width"] = width;
420 root["height"] = height;
421 root["x_offset"] = x_offset;
422 root["y_offset"] = y_offset;
423 root["text"] = text;
424 root["font"] = font.toString().toStdString();
425 root["text_color"] = text_color;
426 root["background_color"] = background_color;
427 root["text_background_color"] = text_background_color;
428 root["gravity"] = gravity;
429
430 // return JsonValue
431 return root;
432}
433
434// Load JSON string into this object
435void QtTextReader::SetJson(const std::string value) {
436
437 // Parse JSON string into JSON objects
438 try
439 {
440 const Json::Value root = openshot::stringToJson(value);
441 // Set all values that match
442 SetJsonValue(root);
443 }
444 catch (const std::exception& e)
445 {
446 // Error parsing JSON (or missing keys)
447 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
448 }
449}
450
451// Load Json::Value into this object
452void QtTextReader::SetJsonValue(const Json::Value root) {
453
454 // Set parent data
456
457 // Set data from Json (if key is found)
458 if (!root["width"].isNull())
459 width = root["width"].asInt();
460 if (!root["height"].isNull())
461 height = root["height"].asInt();
462 if (!root["x_offset"].isNull())
463 x_offset = root["x_offset"].asInt();
464 if (!root["y_offset"].isNull())
465 y_offset = root["y_offset"].asInt();
466 if (!root["text"].isNull())
467 text = root["text"].asString();
468 if (!root["font"].isNull())
469 font.fromString(QString::fromStdString(root["font"].asString()));
470 if (!root["text_color"].isNull())
471 text_color = root["text_color"].asString();
472 if (!root["background_color"].isNull())
473 background_color = root["background_color"].asString();
474 if (!root["text_background_color"].isNull())
475 text_background_color = root["text_background_color"].asString();
476 if (!root["gravity"].isNull())
477 gravity = (GravityType) root["gravity"].asInt();
478
479 if (root.isMember("text_animation")) text_animation = root["text_animation"];
480 if (!root["document_layout"].isNull()) document_layout = root["document_layout"].asBool();
481 if (!root["background_padding"].isNull()) background_padding = std::max(0.0, root["background_padding"].asDouble());
482 if (!root["stroke_width"].isNull()) stroke_width = std::max(0.0, root["stroke_width"].asDouble());
483 if (!root["stroke"].isNull()) stroke = root["stroke"].asString();
484 if (!root["border_width"].isNull()) border_width = std::max(0.0, root["border_width"].asDouble());
485 if (!root["border"].isNull()) border = root["border"].asString();
486 if (!root["letter_spacing"].isNull()) letter_spacing = root["letter_spacing"].asDouble();
487 if (!root["line_spacing"].isNull()) line_spacing = std::max(0.1, root["line_spacing"].asDouble());
488 if (!root["background_corner"].isNull()) background_corner = std::max(0.0, root["background_corner"].asDouble());
489 width = std::max(1, width);
490 height = std::max(1, height);
491 if (root["fps"].isObject() && root["fps"]["num"].asInt() > 0 && root["fps"]["den"].asInt() > 0)
492 source_fps = Fraction(root["fps"]["num"].asInt(), root["fps"]["den"].asInt());
493 render_cache.clear();
494 UpdateInfo();
495
496 // Re-Open path, and re-init everything (if needed)
497 if (is_open)
498 {
499 Close();
500 Open();
501 }
502}
Header file for CacheBase class.
Header file for Clip class.
Header file for Color class.
Header file for all Exception classes.
Header file for Frame class.
Header file for QtTextReader class.
Header file for Timeline class.
This class represents a clip (used to arrange readers on the timeline)
Definition: Clip.h:89
openshot::Keyframe scale_x
Curve representing the horizontal scaling in percent (0 to 1)
Definition: Clip.h:334
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
Definition: Clip.h:310
openshot::Keyframe scale_y
Curve representing the vertical scaling in percent (0 to 1)
Definition: Clip.h:335
openshot::ScaleType scale
Definition: Clip.h:186
This class represents a color (used on the timeline and clips)
Definition: Color.h:27
double Y
The Y value of the coordinate (usually representing the value of the property being animated)
Definition: Coordinate.h:41
This class represents a fraction.
Definition: Fraction.h:30
int num
Numerator for the fraction.
Definition: Fraction.h:32
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:40
Fraction Reciprocal() const
Return the reciprocal as a Fraction.
Definition: Fraction.cpp:78
int den
Denominator for the fraction.
Definition: Fraction.h:33
int GetSamplesPerFrame(openshot::Fraction fps, int sample_rate, int channels)
Calculate the # of samples per video frame (for the current frame number)
Definition: Frame.cpp:486
Exception for invalid JSON.
Definition: Exceptions.h:224
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Definition: KeyFrame.h:53
Point GetMaxPoint() const
Get max point (by Y coordinate)
Definition: KeyFrame.cpp:245
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258
Coordinate co
This is the primary coordinate.
Definition: Point.h:66
void SetJson(const std::string value) override
Load JSON string into this object.
void SetTextBackgroundColor(std::string color)
void Close() override
Close Reader.
std::string Json() const override
Generate JSON string of this object.
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
QtTextReader()
Default constructor (blank text)
std::shared_ptr< openshot::Frame > GetFrame(int64_t requested_frame) override
Json::Value JsonValue() const override
Generate Json::Value for this object.
void Open() override
Open Reader - which is called by the constructor automatically.
openshot::ReaderInfo info
Information about the current media file.
Definition: ReaderBase.h:91
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ReaderBase.cpp:161
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:110
std::recursive_mutex getFrameMutex
Mutex for multiple threads.
Definition: ReaderBase.h:79
openshot::ClipBase * ParentClip()
Parent clip object of this reader (which can be unparented and NULL)
Definition: ReaderBase.cpp:244
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:45
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
Definition: TimelineBase.h:44
This class represents a timeline.
Definition: Timeline.h:154
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
GravityType
This enumeration determines how clips are aligned to their parent container.
Definition: Enums.h:22
@ GRAVITY_TOP_LEFT
Align clip to the top left of its parent.
Definition: Enums.h:23
@ GRAVITY_LEFT
Align clip to the left of its parent (middle aligned)
Definition: Enums.h:26
@ GRAVITY_TOP_RIGHT
Align clip to the top right of its parent.
Definition: Enums.h:25
@ GRAVITY_RIGHT
Align clip to the right of its parent (middle aligned)
Definition: Enums.h:28
@ GRAVITY_BOTTOM_LEFT
Align clip to the bottom left of its parent.
Definition: Enums.h:29
@ GRAVITY_BOTTOM
Align clip to the bottom center of its parent.
Definition: Enums.h:30
@ GRAVITY_TOP
Align clip to the top center of its parent.
Definition: Enums.h:24
@ GRAVITY_BOTTOM_RIGHT
Align clip to the bottom right of its parent.
Definition: Enums.h:31
@ GRAVITY_CENTER
Align clip to the center of its parent (middle aligned)
Definition: Enums.h:27
@ SCALE_STRETCH
Scale the clip until both height and width fill the canvas (distort to fit)
Definition: Enums.h:39
@ SCALE_CROP
Scale the clip until both height and width fill the canvas (cropping the overlap)
Definition: Enums.h:37
@ SCALE_NONE
Do not scale the clip.
Definition: Enums.h:40
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
bool has_single_image
Determines if this file only contains a single image.
Definition: ReaderBase.h:42
float duration
Length of time (in seconds)
Definition: ReaderBase.h:43
int width
The width of the video (in pixesl)
Definition: ReaderBase.h:46
int channels
The number of audio channels used in the audio stream.
Definition: ReaderBase.h:61
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
Definition: ReaderBase.h:48
openshot::Fraction display_ratio
The ratio of width to height of the video stream (i.e. 640x480 has a ratio of 4/3)
Definition: ReaderBase.h:51
int height
The height of the video (in pixels)
Definition: ReaderBase.h:45
int64_t video_length
The number of frames in the video stream.
Definition: ReaderBase.h:53
std::string acodec
The name of the audio codec used to encode / decode the video stream.
Definition: ReaderBase.h:58
std::string vcodec
The name of the video codec used to encode / decode the video stream.
Definition: ReaderBase.h:52
openshot::Fraction pixel_ratio
The pixel ratio of the video stream as a fraction (i.e. some pixels are not square)
Definition: ReaderBase.h:50
bool has_video
Determines if this file has a video stream.
Definition: ReaderBase.h:40
bool has_audio
Determines if this file has an audio stream.
Definition: ReaderBase.h:41
openshot::Fraction video_timebase
The video timebase determines how long each frame stays on the screen.
Definition: ReaderBase.h:55
int sample_rate
The number of audio samples per second (44100 is a common sample rate)
Definition: ReaderBase.h:60
int64_t file_size
Size of file (in bytes)
Definition: ReaderBase.h:44