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; }