diff --git a/include/Clip.h b/include/Clip.h index a609cb85..fc8c828e 100644 --- a/include/Clip.h +++ b/include/Clip.h @@ -119,7 +119,7 @@ namespace openshot { std::shared_ptr apply_effects(std::shared_ptr frame); /// Apply keyframes to the source frame (if any) - std::shared_ptr apply_keyframes(std::shared_ptr frame); + std::shared_ptr apply_keyframes(std::shared_ptr frame, int width, int height); /// Get file extension std::string get_file_extension(std::string path); @@ -191,12 +191,24 @@ namespace openshot { /// Look up an effect by ID openshot::EffectBase* GetEffect(const std::string& id); - /// @brief Get an openshot::Frame object for a specific frame number of this timeline. + /// @brief Get an openshot::Frame object for a specific frame number of this timeline. The image size and number + /// of samples match the source reader. /// /// @returns The requested frame (containing the image) /// @param requested_frame The frame number that is requested std::shared_ptr GetFrame(int64_t requested_frame); + /// @brief Get an openshot::Frame object for a specific frame number of this timeline. The image size and number + /// of samples can be customized to match the Timeline, or any custom output. Extra samples will be moved to the + /// next Frame. Missing samples will be moved from the next Frame. + /// + /// @returns The requested frame (containing the image) + /// @param requested_frame The frame number that is requested + /// @param width The width of the image requested + /// @param height The height of the image requested + /// @param samples The number of samples requested + std::shared_ptr GetFrame(int64_t requested_frame, int width, int height, int samples); + /// Open the internal reader void Open(); diff --git a/src/Clip.cpp b/src/Clip.cpp index 8e143547..4aead66d 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -325,6 +325,33 @@ std::shared_ptr Clip::GetFrame(int64_t requested_frame) // Adjust out of bounds frame number requested_frame = adjust_frame_number_minimum(requested_frame); + // Is a time map detected + int64_t new_frame_number = requested_frame; + int64_t time_mapped_number = adjust_frame_number_minimum(time.GetLong(requested_frame)); + if (time.GetLength() > 1) + new_frame_number = time_mapped_number; + + // Get the # of audio samples from the time mapped Frame instance + std::shared_ptr time_mapped_original_frame = GetOrCreateFrame(new_frame_number); + return GetFrame(requested_frame, reader->info.width, reader->info.height, time_mapped_original_frame->GetAudioSamplesCount()); + } + else + // Throw error if reader not initialized + throw ReaderClosed("No Reader has been initialized for this Clip. Call Reader(*reader) before calling this method."); +} + +// Get an openshot::Frame object for a specific frame number of this reader. +std::shared_ptr Clip::GetFrame(int64_t requested_frame, int width, int height, int samples) +{ + // Check for open reader (or throw exception) + if (!is_open) + throw ReaderClosed("The Clip is closed. Call Open() before calling this method", "N/A"); + + if (reader) + { + // Adjust out of bounds frame number + requested_frame = adjust_frame_number_minimum(requested_frame); + // Adjust has_video and has_audio overrides int enabled_audio = has_audio.GetInt(requested_frame); if (enabled_audio == -1 && reader && reader->info.has_audio) @@ -367,13 +394,17 @@ std::shared_ptr Clip::GetFrame(int64_t requested_frame) frame->AddAudio(true, channel, 0, original_frame->GetAudioSamples(channel), original_frame->GetAudioSamplesCount(), 1.0); // Get time mapped frame number (used to increase speed, change direction, etc...) + // TODO: Handle variable # of samples, since this resamples audio for different speeds (only when time curve is set) get_time_mapped_frame(frame, requested_frame); + // Adjust # of samples to match requested (the interaction with time curves will make this tricky) + // TODO: Implement move samples to/from next frame + // Apply effects to the frame (if any) apply_effects(frame); // Apply keyframe / transforms - apply_keyframes(frame); + apply_keyframes(frame, width, height); // Return processed 'frame' return frame; @@ -646,13 +677,9 @@ int64_t Clip::adjust_frame_number_minimum(int64_t frame_number) std::shared_ptr Clip::GetOrCreateFrame(int64_t number) { std::shared_ptr new_frame; - - // Init some basic properties about this frame - int samples_in_frame = Frame::GetSamplesPerFrame(number, reader->info.fps, reader->info.sample_rate, reader->info.channels); - try { // Debug output - ZmqLogger::Instance()->AppendDebugMethod("Clip::GetOrCreateFrame (from reader)", "number", number, "samples_in_frame", samples_in_frame); + ZmqLogger::Instance()->AppendDebugMethod("Clip::GetOrCreateFrame (from reader)", "number", number); // Attempt to get a frame (but this could fail if a reader has just been closed) new_frame = reader->GetFrame(number); @@ -669,14 +696,17 @@ std::shared_ptr Clip::GetOrCreateFrame(int64_t number) // ... } + // Estimate # of samples needed for this frame + int estimated_samples_in_frame = Frame::GetSamplesPerFrame(number, reader->info.fps, reader->info.sample_rate, reader->info.channels); + // Debug output - ZmqLogger::Instance()->AppendDebugMethod("Clip::GetOrCreateFrame (create blank)", "number", number, "samples_in_frame", samples_in_frame); + ZmqLogger::Instance()->AppendDebugMethod("Clip::GetOrCreateFrame (create blank)", "number", number, "estimated_samples_in_frame", estimated_samples_in_frame); // Create blank frame - new_frame = std::make_shared(number, reader->info.width, reader->info.height, "#000000", samples_in_frame, reader->info.channels); + new_frame = std::make_shared(number, reader->info.width, reader->info.height, "#000000", estimated_samples_in_frame, reader->info.channels); new_frame->SampleRate(reader->info.sample_rate); new_frame->ChannelsLayout(reader->info.channel_layout); - new_frame->AddAudioSilence(samples_in_frame); + new_frame->AddAudioSilence(estimated_samples_in_frame); return new_frame; } @@ -1078,7 +1108,7 @@ bool Clip::isEqual(double a, double b) // Apply keyframes to the source frame (if any) -std::shared_ptr Clip::apply_keyframes(std::shared_ptr frame) +std::shared_ptr Clip::apply_keyframes(std::shared_ptr frame, int width, int height) { // Get actual frame image data std::shared_ptr source_image = frame->GetImage(); @@ -1096,7 +1126,7 @@ std::shared_ptr Clip::apply_keyframes(std::shared_ptr frame) int alpha = wave_color.alpha.GetInt(frame->number); // Generate Waveform Dynamically (the size of the timeline) - source_image = frame->GetWaveform(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, red, green, blue, alpha); + source_image = frame->GetWaveform(width, height, red, green, blue, alpha); frame->AddImage(std::shared_ptr(source_image)); } @@ -1125,7 +1155,7 @@ std::shared_ptr Clip::apply_keyframes(std::shared_ptr frame) { case (SCALE_FIT): { // keep aspect ratio - source_size.scale(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, Qt::KeepAspectRatio); + source_size.scale(width, height, Qt::KeepAspectRatio); // Debug output ZmqLogger::Instance()->AppendDebugMethod("Clip::add_keyframes (Scale: SCALE_FIT)", "frame->number", frame->number, "source_width", source_size.width(), "source_height", source_size.height()); @@ -1133,18 +1163,18 @@ std::shared_ptr Clip::apply_keyframes(std::shared_ptr frame) } case (SCALE_STRETCH): { // ignore aspect ratio - source_size.scale(Settings::Instance()->MAX_WIDTH, Settings::Instance()->MAX_HEIGHT, Qt::IgnoreAspectRatio); + source_size.scale(width, height, Qt::IgnoreAspectRatio); // Debug output ZmqLogger::Instance()->AppendDebugMethod("Clip::add_keyframes (Scale: SCALE_STRETCH)", "frame->number", frame->number, "source_width", source_size.width(), "source_height", source_size.height()); break; } case (SCALE_CROP): { - QSize width_size(Settings::Instance()->MAX_WIDTH, round(Settings::Instance()->MAX_WIDTH / (float(source_size.width()) / float(source_size.height())))); - QSize height_size(round(Settings::Instance()->MAX_HEIGHT / (float(source_size.height()) / float(source_size.width()))), Settings::Instance()->MAX_HEIGHT); + QSize width_size(width, round(width / (float(source_size.width()) / float(source_size.height())))); + QSize height_size(round(height / (float(source_size.height()) / float(source_size.width()))), height); // respect aspect ratio - if (width_size.width() >= Settings::Instance()->MAX_WIDTH && width_size.height() >= Settings::Instance()->MAX_HEIGHT) + if (width_size.width() >= width && width_size.height() >= height) source_size.scale(width_size.width(), width_size.height(), Qt::KeepAspectRatio); else source_size.scale(height_size.width(), height_size.height(), Qt::KeepAspectRatio); @@ -1157,9 +1187,9 @@ std::shared_ptr Clip::apply_keyframes(std::shared_ptr frame) // Calculate ratio of source size to project size // Even with no scaling, previews need to be adjusted correctly // (otherwise NONE scaling draws the frame image outside of the preview) - float source_width_ratio = source_size.width() / float(Settings::Instance()->MAX_WIDTH); - float source_height_ratio = source_size.height() / float(Settings::Instance()->MAX_HEIGHT); - source_size.scale(Settings::Instance()->MAX_WIDTH * source_width_ratio, Settings::Instance()->MAX_HEIGHT * source_height_ratio, Qt::KeepAspectRatio); + float source_width_ratio = source_size.width() / float(width); + float source_height_ratio = source_size.height() / float(height); + source_size.scale(width * source_width_ratio, height * source_height_ratio, Qt::KeepAspectRatio); // Debug output ZmqLogger::Instance()->AppendDebugMethod("Clip::add_keyframes (Scale: SCALE_NONE)", "frame->number", frame->number, "source_width", source_size.width(), "source_height", source_size.height()); @@ -1222,32 +1252,32 @@ std::shared_ptr Clip::apply_keyframes(std::shared_ptr frame) // This is only here to prevent unused-enum warnings break; case (GRAVITY_TOP): - x = (Settings::Instance()->MAX_WIDTH - scaled_source_width) / 2.0; // center + x = (width - scaled_source_width) / 2.0; // center break; case (GRAVITY_TOP_RIGHT): - x = Settings::Instance()->MAX_WIDTH - scaled_source_width; // right + x = width - scaled_source_width; // right break; case (GRAVITY_LEFT): - y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height) / 2.0; // center + y = (height - scaled_source_height) / 2.0; // center break; case (GRAVITY_CENTER): - x = (Settings::Instance()->MAX_WIDTH - scaled_source_width) / 2.0; // center - y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height) / 2.0; // center + x = (width - scaled_source_width) / 2.0; // center + y = (height - scaled_source_height) / 2.0; // center break; case (GRAVITY_RIGHT): - x = Settings::Instance()->MAX_WIDTH - scaled_source_width; // right - y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height) / 2.0; // center + x = width - scaled_source_width; // right + y = (height - scaled_source_height) / 2.0; // center break; case (GRAVITY_BOTTOM_LEFT): - y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height); // bottom + y = (height - scaled_source_height); // bottom break; case (GRAVITY_BOTTOM): - x = (Settings::Instance()->MAX_WIDTH - scaled_source_width) / 2.0; // center - y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height); // bottom + x = (width - scaled_source_width) / 2.0; // center + y = (height - scaled_source_height); // bottom break; case (GRAVITY_BOTTOM_RIGHT): - x = Settings::Instance()->MAX_WIDTH - scaled_source_width; // right - y = (Settings::Instance()->MAX_HEIGHT - scaled_source_height); // bottom + x = width - scaled_source_width; // right + y = (height - scaled_source_height); // bottom break; } @@ -1256,8 +1286,8 @@ std::shared_ptr Clip::apply_keyframes(std::shared_ptr frame) /* LOCATION, ROTATION, AND SCALE */ float r = rotation.GetValue(frame->number); // rotate in degrees - x += (Settings::Instance()->MAX_WIDTH * location_x.GetValue(frame->number)); // move in percentage of final width - y += (Settings::Instance()->MAX_HEIGHT * location_y.GetValue(frame->number)); // move in percentage of final height + x += (width * location_x.GetValue(frame->number)); // move in percentage of final width + y += (height * location_y.GetValue(frame->number)); // move in percentage of final height float shear_x_value = shear_x.GetValue(frame->number); float shear_y_value = shear_y.GetValue(frame->number); float origin_x_value = origin_x.GetValue(frame->number); diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 42009a22..cf3f0cee 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -441,7 +441,7 @@ std::shared_ptr Timeline::GetOrCreateFrame(Clip* clip, int64_t number) // Attempt to get a frame (but this could fail if a reader has just been closed) #pragma omp critical (T_GetOtCreateFrame) - new_frame = std::shared_ptr(clip->GetFrame(number)); + new_frame = std::shared_ptr(clip->GetFrame(number, info.width, info.height, samples_in_frame)); // Return real frame return new_frame; @@ -737,8 +737,10 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) // Get clip frame # long clip_start_frame = (clip->Start() * info.fps.ToDouble()) + 1; long clip_frame_number = frame_number - clip_start_position + clip_start_frame; + int samples_in_frame = Frame::GetSamplesPerFrame(frame_number, info.fps, info.sample_rate, info.channels); + // Cache clip object - clip->GetFrame(clip_frame_number); + clip->GetFrame(clip_frame_number, info.width, info.height, samples_in_frame); } } }