From f2b0f3a0f4612bd34080d9e4e62cc271a718ecc1 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sat, 3 Feb 2018 01:57:18 -0600 Subject: [PATCH] Adding metadata from format, audio stream, and video streams to ReaderBase.info, which in some cases includes the 'rotate' metadata added by certain cameras, and audio metadata like title, album, artist, copyright, dates, etc... Auto-Rotates any Clip with Reader metadata 'rotate' attribute. --- include/Clip.h | 3 +++ include/Frame.h | 2 +- include/ReaderBase.h | 1 + src/Clip.cpp | 44 ++++++++++++++++++++++++++++------ src/FFmpegReader.cpp | 23 +++++++++++++++++- src/Frame.cpp | 18 ++++++++++---- src/ReaderBase.cpp | 19 +++++++++++++++ src/bindings/python/openshot.i | 2 ++ src/bindings/ruby/openshot.i | 2 ++ 9 files changed, 101 insertions(+), 13 deletions(-) diff --git a/include/Clip.h b/include/Clip.h index f8f4b340..ea54162f 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -141,6 +141,9 @@ namespace openshot { /// Init default settings for a clip void init_settings(); + /// Update default rotation from reader + void init_reader_rotation(); + /// Sort effects by order void sort_effects(); diff --git a/include/Frame.h b/include/Frame.h index ccaf21e2..841468fd 100644 --- a/include/Frame.h +++ b/include/Frame.h @@ -286,7 +286,7 @@ namespace openshot /// Thumbnail the frame image with tons of options to the specified path. The image format is determined from the extension (i.e. image.PNG, image.JPEG). /// This method allows for masks, overlays, background color, and much more accurate resizing (including padding and centering) void Thumbnail(string path, int new_width, int new_height, string mask_path, string overlay_path, - string background_color, bool ignore_aspect, string format="png", int quality=100); + string background_color, bool ignore_aspect, string format="png", int quality=100, float rotate=0.0); /// Play audio samples for this frame void Play(); diff --git a/include/ReaderBase.h b/include/ReaderBase.h index cbb6b57b..2b3ee917 100644 --- a/include/ReaderBase.h +++ b/include/ReaderBase.h @@ -83,6 +83,7 @@ namespace openshot ChannelLayout channel_layout; ///< The channel layout (mono, stereo, 5 point surround, etc...) int audio_stream_index; ///< The index of the audio stream Fraction audio_timebase; ///< The audio timebase determines how long each audio packet should be played + std::map metadata; ///< An optional map/dictionary of metadata for this reader }; /** diff --git a/src/Clip.cpp b/src/Clip.cpp index 083348ce..f80705c9 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -52,9 +52,11 @@ void Clip::init_settings() location_x = Keyframe(0.0); location_y = Keyframe(0.0); - // Init alpha & rotation + // Init alpha alpha = Keyframe(1.0); - rotation = Keyframe(0.0); + + // Init rotation + init_reader_rotation(); // Init time & volume time = Keyframe(1.0); @@ -97,8 +99,32 @@ void Clip::init_settings() manage_reader = false; } +// Init reader's rotation (if any) +void Clip::init_reader_rotation() { + // Only init rotation from reader when needed + if (rotation.Points.size() > 1) + // Do nothing if more than 1 rotation Point + return; + else if (rotation.Points.size() == 1 && rotation.GetValue(1) != 0.0) + // Do nothing if 1 Point, and it's not the default value + return; + + // Init rotation + if (reader && reader->info.metadata.count("rotate") > 0) { + // Use reader metadata rotation (if any) + // This is typical with cell phone videos filmed in different orientations + try { + float rotate_metadata = strtof(reader->info.metadata["rotate"].c_str(), 0); + rotation = Keyframe(rotate_metadata); + } catch (exception e) {} + } + else + // Default no rotation + rotation = Keyframe(0.0); +} + // Default Constructor for a clip -Clip::Clip() +Clip::Clip() : reader(NULL) { // Init all default settings init_settings(); @@ -107,12 +133,12 @@ Clip::Clip() // Constructor with reader Clip::Clip(ReaderBase* new_reader) { - // Init all default settings - init_settings(); - // Set the reader reader = new_reader; + // Init all default settings + init_settings(); + // Open and Close the reader (to set the duration of the clip) Open(); Close(); @@ -122,7 +148,7 @@ Clip::Clip(ReaderBase* new_reader) } // Constructor with filepath -Clip::Clip(string path) +Clip::Clip(string path) : reader(NULL) { // Init all default settings init_settings(); @@ -165,6 +191,7 @@ Clip::Clip(string path) if (reader) { End(reader->info.duration); manage_reader = true; + init_reader_rotation(); } } @@ -189,6 +216,9 @@ void Clip::Reader(ReaderBase* new_reader) { // set reader pointer reader = new_reader; + + // Init rotation (if any) + init_reader_rotation(); } /// Get the current reader diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index f14f316e..f803c86f 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -186,6 +186,14 @@ void FFmpegReader::Open() UpdateAudioInfo(); } + // Add format metadata (if any) + AVDictionaryEntry *tag = NULL; + while ((tag = av_dict_get(pFormatCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + QString str_key = tag->key; + QString str_value = tag->value; + info.metadata[str_key.toStdString()] = str_value.trimmed().toStdString(); + } + // Init previous audio location to zero previous_packet_location.frame = -1; previous_packet_location.sample_start = 0; @@ -294,9 +302,15 @@ void FFmpegReader::UpdateAudioInfo() info.video_length = info.duration * info.fps.ToDouble(); info.width = 720; info.height = 480; - } + // Add audio metadata (if any found) + AVDictionaryEntry *tag = NULL; + while ((tag = av_dict_get(aStream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + QString str_key = tag->key; + QString str_value = tag->value; + info.metadata[str_key.toStdString()] = str_value.trimmed().toStdString(); + } } void FFmpegReader::UpdateVideoInfo() @@ -390,6 +404,13 @@ void FFmpegReader::UpdateVideoInfo() info.video_length = round(info.duration * info.fps.ToDouble()); } + // Add video metadata (if any) + AVDictionaryEntry *tag = NULL; + while ((tag = av_dict_get(pStream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + QString str_key = tag->key; + QString str_value = tag->value; + info.metadata[str_key.toStdString()] = str_value.trimmed().toStdString(); + } } diff --git a/src/Frame.cpp b/src/Frame.cpp index 9155f1ee..738cfba9 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -578,18 +578,16 @@ void Frame::Save(string path, float scale, string format, int quality) // Thumbnail the frame image to the specified path. The image format is determined from the extension (i.e. image.PNG, image.JPEG) void Frame::Thumbnail(string path, int new_width, int new_height, string mask_path, string overlay_path, - string background_color, bool ignore_aspect, string format, int quality) { + string background_color, bool ignore_aspect, string format, int quality, float rotate) { // Create blank thumbnail image & fill background color std::shared_ptr thumbnail = std::shared_ptr(new QImage(new_width, new_height, QImage::Format_RGBA8888)); thumbnail->fill(QColor(QString::fromStdString(background_color))); - // Create transform and painter - QTransform transform; + // Create painter QPainter painter(thumbnail.get()); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing, true); - // Get preview image std::shared_ptr previewImage = GetImage(); @@ -616,6 +614,18 @@ void Frame::Thumbnail(string path, int new_width, int new_height, string mask_pa int x = (new_width - previewImage->size().width()) / 2.0; // center int y = (new_height - previewImage->size().height()) / 2.0; // center painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + + + // Create transform and rotate (if needed) + QTransform transform; + float origin_x = previewImage->width() / 2.0; + float origin_y = previewImage->height() / 2.0; + transform.translate(origin_x, origin_y); + transform.rotate(rotate); + transform.translate(-origin_x,-origin_y); + painter.setTransform(transform); + + // Draw image onto QImage painter.drawImage(x, y, *previewImage); diff --git a/src/ReaderBase.cpp b/src/ReaderBase.cpp index ff5e43c7..5de6fdff 100644 --- a/src/ReaderBase.cpp +++ b/src/ReaderBase.cpp @@ -100,6 +100,13 @@ void ReaderBase::DisplayInfo() { cout << "--> Audio Stream Index: " << info.audio_stream_index << endl; cout << "--> Audio Timebase: " << info.audio_timebase.ToDouble() << " (" << info.audio_timebase.num << "/" << info.audio_timebase.den << ")" << endl; cout << "----------------------------" << endl; + cout << "--------- Metadata ---------" << endl; + cout << "----------------------------" << endl; + + // Iterate through metadata + map::iterator it; + for (it = info.metadata.begin(); it != info.metadata.end(); it++) + cout << "--> " << it->first << ": " << it->second << endl; } // Generate Json::JsonValue for this object @@ -147,6 +154,12 @@ Json::Value ReaderBase::JsonValue() { root["audio_timebase"]["num"] = info.audio_timebase.num; root["audio_timebase"]["den"] = info.audio_timebase.den; + // Append metadata map + root["metadata"] = Json::Value(Json::objectValue); + map::iterator it; + for (it = info.metadata.begin(); it != info.metadata.end(); it++) + root["metadata"][it->first] = it->second; + // return JsonValue return root; } @@ -226,4 +239,10 @@ void ReaderBase::SetJsonValue(Json::Value root) { if (!root["audio_timebase"]["den"].isNull()) info.audio_timebase.den = root["audio_timebase"]["den"].asInt(); } + if (!root["metadata"].isNull() && root["metadata"].isObject()) { + for( Json::Value::iterator itr = root["metadata"].begin() ; itr != root["metadata"].end() ; itr++ ) { + string key = itr.key().asString(); + info.metadata[key] = root["metadata"][key].asString(); + } + } } diff --git a/src/bindings/python/openshot.i b/src/bindings/python/openshot.i index 4841a00e..87afe6d5 100644 --- a/src/bindings/python/openshot.i +++ b/src/bindings/python/openshot.i @@ -35,6 +35,7 @@ %include "std_string.i" %include "std_list.i" %include "std_vector.i" +%include "std_map.i" %include /* Unhandled STL Exception Handling */ @@ -176,4 +177,5 @@ namespace std { %template(PointsVector) vector; %template(FieldVector) vector; %template(MappedFrameVector) vector; + %template(MappedMetadata) map; } diff --git a/src/bindings/ruby/openshot.i b/src/bindings/ruby/openshot.i index 54b2ac43..f27476c0 100644 --- a/src/bindings/ruby/openshot.i +++ b/src/bindings/ruby/openshot.i @@ -35,6 +35,7 @@ %include "std_string.i" %include "std_list.i" %include "std_vector.i" +%include "std_map.i" /* Unhandled STL Exception Handling */ %include @@ -169,4 +170,5 @@ namespace std { %template(PointsVector) vector; %template(FieldVector) vector; %template(MappedFrameVector) vector; + %template(MappedMetadata) map; }