音频导出

This commit is contained in:
Sch 2023-09-09 14:59:45 +08:00
parent 8005d78372
commit 7613ea06cd
30 changed files with 471 additions and 146 deletions

BIN
Asset/1.wav Normal file

Binary file not shown.

BIN
Asset/A.mp3 Normal file

Binary file not shown.

3
Asset/Start.bat Normal file
View File

@ -0,0 +1,3 @@
D:\Project\Cut5\Binaries\Win64\ffmpeg.exe -i 1.wav -i A.mp3 -filter_complex "[0:a]pan=1c|c0=c0[a];[1:a]pan=1c|c0=c1[b];[a][b]amerge=inputs=2[aout]" -map "[aout]" -t 1000000 output.mp3
pause

BIN
Asset/output.mp3 Normal file

Binary file not shown.

View File

@ -7,49 +7,67 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Engine", "Engine", "{233774
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Games", "Games", "{DE1F8B53-6C02-3C13-9101-A7C8D96F3FF6}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UE5", "Intermediate\ProjectFiles\UE5.vcxproj", "{6EE39883-7339-3FB6-AD82-931FB137D37F}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Cut5", "Intermediate\ProjectFiles\Cut5.vcxproj", "{B95E7D0E-DB45-3765-9058-E00EBBC4B157}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Cut5", "Intermediate\ProjectFiles\Cut5.vcxproj", "{AF5A253A-0F37-38CE-8998-45CA936C112B}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UE5", "Intermediate\ProjectFiles\UE5.vcxproj", "{C48D0E9D-C862-3EA3-96A7-752EE9D06362}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Visualizers", "Visualizers", "{1CCEC849-CC72-4C59-8C36-2F7C38706D4C}"
ProjectSection(SolutionItems) = preProject
D:\UE\UE_5.2\Engine\Extras\VisualStudioDebugging\Unreal.natvis = D:\UE\UE_5.2\Engine\Extras\VisualStudioDebugging\Unreal.natvis
..\..\Software\UE_5.2\Engine\Extras\VisualStudioDebugging\Unreal.natvis = ..\..\Software\UE_5.2\Engine\Extras\VisualStudioDebugging\Unreal.natvis
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
DebugGame Editor|Android = DebugGame Editor|Android
DebugGame Editor|Win64 = DebugGame Editor|Win64
DebugGame|Android = DebugGame|Android
DebugGame|Win64 = DebugGame|Win64
Development Editor|Android = Development Editor|Android
Development Editor|Win64 = Development Editor|Win64
Development|Android = Development|Android
Development|Win64 = Development|Win64
Shipping|Android = Shipping|Android
Shipping|Win64 = Shipping|Win64
EndGlobalSection
# UnrealVS Section
GlobalSection(ddbf523f-7eb6-4887-bd51-85a714ff87eb) = preSolution
AvailablePlatforms=Win64
AvailablePlatforms=Win64;Android
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6EE39883-7339-3FB6-AD82-931FB137D37F}.DebugGame Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{6EE39883-7339-3FB6-AD82-931FB137D37F}.DebugGame|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{6EE39883-7339-3FB6-AD82-931FB137D37F}.Development Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{6EE39883-7339-3FB6-AD82-931FB137D37F}.Development|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{6EE39883-7339-3FB6-AD82-931FB137D37F}.Shipping|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{AF5A253A-0F37-38CE-8998-45CA936C112B}.DebugGame Editor|Win64.ActiveCfg = DebugGame_Editor|x64
{AF5A253A-0F37-38CE-8998-45CA936C112B}.DebugGame Editor|Win64.Build.0 = DebugGame_Editor|x64
{AF5A253A-0F37-38CE-8998-45CA936C112B}.DebugGame|Win64.ActiveCfg = DebugGame|x64
{AF5A253A-0F37-38CE-8998-45CA936C112B}.DebugGame|Win64.Build.0 = DebugGame|x64
{AF5A253A-0F37-38CE-8998-45CA936C112B}.Development Editor|Win64.ActiveCfg = Development_Editor|x64
{AF5A253A-0F37-38CE-8998-45CA936C112B}.Development Editor|Win64.Build.0 = Development_Editor|x64
{AF5A253A-0F37-38CE-8998-45CA936C112B}.Development|Win64.ActiveCfg = Development|x64
{AF5A253A-0F37-38CE-8998-45CA936C112B}.Development|Win64.Build.0 = Development|x64
{AF5A253A-0F37-38CE-8998-45CA936C112B}.Shipping|Win64.ActiveCfg = Shipping|x64
{AF5A253A-0F37-38CE-8998-45CA936C112B}.Shipping|Win64.Build.0 = Shipping|x64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame Editor|Android.ActiveCfg = Invalid|x64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame Editor|Win64.ActiveCfg = DebugGame_Editor|x64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame Editor|Win64.Build.0 = DebugGame_Editor|x64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame|Android.ActiveCfg = Android_DebugGame|Win64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame|Android.Build.0 = Android_DebugGame|Win64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame|Win64.ActiveCfg = DebugGame|x64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame|Win64.Build.0 = DebugGame|x64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development Editor|Android.ActiveCfg = Invalid|x64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development Editor|Win64.ActiveCfg = Development_Editor|x64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development Editor|Win64.Build.0 = Development_Editor|x64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development|Android.ActiveCfg = Android_Development|Win64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development|Android.Build.0 = Android_Development|Win64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development|Win64.ActiveCfg = Development|x64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development|Win64.Build.0 = Development|x64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Shipping|Android.ActiveCfg = Android_Shipping|Win64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Shipping|Android.Build.0 = Android_Shipping|Win64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Shipping|Win64.ActiveCfg = Shipping|x64
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Shipping|Win64.Build.0 = Shipping|x64
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.DebugGame Editor|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.DebugGame Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.DebugGame|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.DebugGame|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.Development Editor|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.Development Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.Development|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.Development|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.Shipping|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win64
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.Shipping|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{6EE39883-7339-3FB6-AD82-931FB137D37F} = {233774A8-CC9D-3FA9-86D1-90573E92B704}
{AF5A253A-0F37-38CE-8998-45CA936C112B} = {DE1F8B53-6C02-3C13-9101-A7C8D96F3FF6}
{C48D0E9D-C862-3EA3-96A7-752EE9D06362} = {233774A8-CC9D-3FA9-86D1-90573E92B704}
{B95E7D0E-DB45-3765-9058-E00EBBC4B157} = {DE1F8B53-6C02-3C13-9101-A7C8D96F3FF6}
EndGlobalSection
EndGlobal

View File

@ -368,12 +368,14 @@ TArray<FEncodeVideoInfo> FUtils::TrackEncodeVideo(const FTrackData& TrackData, c
}
TArray<FEncodeVideoInfo> FUtils::TrackEncodeAudio(const FTrackData& TrackData, const FString& ExportPath)
FEncodeVideoInfo FUtils::TrackEncodeAudio(const FTrackData& TrackData, const FString& ExportPath)
{
FPlatformFileManager::Get().GetPlatformFile().CreateDirectoryTree(*FPaths::GetPath(ExportPath));
TArray<FClipData> ClipData = TrackData.ClipData;
ClipData.Sort([](const FClipData& A, const FClipData& B) {return A.ClipStartFrame < B.ClipStartFrame; });
// 先拆出所有音频
FEncodeVideoInfo EncodeVideoInfo;
FClipData SavedClipData;
int32 AudioCount = 0;
{
int32 i = 0;
@ -381,7 +383,7 @@ TArray<FEncodeVideoInfo> FUtils::TrackEncodeAudio(const FTrackData& TrackData, c
{
if (TempClipData.ResourcePropertyDataPtr->Context)
{
FEncodeVideoInfo EncodeVideoInfo;
SavedClipData = TempClipData;
FTimespan EndTimespan = FTimespan::FromSeconds(TempClipData.VideoEndFrame / FGlobalData::GlobalFPS);
FTimespan StartTimespan = FTimespan::FromSeconds(TempClipData.VideoStartFrame / FGlobalData::GlobalFPS);
@ -389,47 +391,50 @@ TArray<FEncodeVideoInfo> FUtils::TrackEncodeAudio(const FTrackData& TrackData, c
FString EndTime = FString::Printf(TEXT("%02d:%02d:%02d"), EndTimespan.GetHours(), EndTimespan.GetMinutes(), EndTimespan.GetSeconds());
FString InputFile = TempClipData.ResourcePropertyDataPtr->MoviePath;
FString OutputFile = FPaths::ConvertRelativePathToFull(GetProjectTempPath() / FString::FromInt(i) + TEXT(".mp3"));
FString OutputFile = ExportPath + TEXT(".mp3");
int32 StartFrame = (TempClipData.VideoStartFrame) % static_cast<int>(FGlobalData::GlobalFPS);;
int32 EndFrame = (TempClipData.VideoEndFrame) % static_cast<int>(FGlobalData::GlobalFPS);
FString Command = FString::Printf(TEXT("-y -i \"%s\" -ss %s -to %s -c copy \"%s\""),
FString Command = FString::Printf(TEXT("-y -i \"%s\" -ss %s -to %s \"%s\""),
*InputFile, *StartTime, *EndTime, *OutputFile);
FPlatformProcess::CreateProc(*GetFfmepg(), *Command, true, false, false, nullptr, 0, nullptr, nullptr);
FProcHandle ProcHandle = FPlatformProcess::CreateProc(*GetFfmepg(), *Command, true, false, false, nullptr, 0, nullptr, nullptr);
while (FPlatformProcess::IsProcRunning(ProcHandle)){ FPlatformProcess::Sleep(0.1);};
i++;
AudioCount++;
}
}
}
FString Header = GetFfmepg();
for (int32 i = 0; i < ClipData.Num(); i++)
if (AudioCount > 1)
{
Header += " -i ";
Header += "\"" + GetProjectTempPath() / FString::FromInt(i) + ".mp3" + "\"";
FString Header = GetFfmepg();
for (int32 i = 0; i < ClipData.Num(); i++)
{
Header += " -i ";
Header += "\"" + GetProjectTempPath() / FString::FromInt(i) + ".mp3" + "\"";
}
Header += " -filter_complex";
// [0:0][1:0]...[n:0]concat=n=n=2:v=0:a=1[out]
for (int32 i = 0; i < ClipData.Num(); i++)
{
Header += "[" + FString::FromInt(i) + ":0]";
}
Header += "concat=n=" + FString::FromInt(ClipData.Num()) + ":v=0:a=1[out]";
Header += " -map [out] -y " + ExportPath + ".mp3";
FProcHandle ProcHandle = FPlatformProcess::CreateProc(*GetFfmepg(), *Header, true, false, false, nullptr, 0, nullptr, nullptr);
while (FPlatformProcess::IsProcRunning(ProcHandle)){ FPlatformProcess::Sleep(0.1);};
}
Header += " -filter_complex";
// [0:0][1:0]...[n:0]concat=n=n=2:v=0:a=1[out]
for (int32 i = 0; i < ClipData.Num(); i++)
{
Header += "[" + FString::FromInt(i) + ":0]";
}
Header += "concat=n=" + FString::FromInt(ClipData.Num()) + ":v=0:a=1[out]";
Header += " -map [out] -y " + ExportPath + ".mp3";
FPlatformProcess::CreateProc(*GetFfmepg(), *Header, true, false, false, nullptr, 0, nullptr, nullptr);
// int32 i = 0;
TArray<FEncodeVideoInfo> EncodeVideoInfos;
// TArray<FEncodeVideoInfo> EncodeVideoInfos;
// for (FClipData& TempClipData : ClipData)
// {
// if (TempClipData.ResourcePropertyDataPtr->Context)
@ -450,8 +455,9 @@ TArray<FEncodeVideoInfo> FUtils::TrackEncodeAudio(const FTrackData& TrackData, c
// FString Command = FString::Printf(TEXT("-y -i \"%s\" -ss %s -to %s -c copy \"%s\""),
// *InputFile, *StartTime, *EndTime, *OutputFile);
//
// FPlatformProcess::CreateProc(*GetFfmepg(), *Command, true, false, false, nullptr, 0, nullptr, nullptr);
//
// FProcHandle ProcHandle = FPlatformProcess::CreateProc(*GetFfmepg(), *Command, true, false, false, nullptr, 0, nullptr, nullptr);
// while (FPlatformProcess::IsProcRunning(ProcHandle)){ FPlatformProcess::Sleep(0.1);};
//
// EncodeVideoInfo.EncodedVideoTimeCode = FGlobalData::GetTimeData(TempClipData.ClipStartFrame);
// EncodeVideoInfo.EncodedVideoName = ExportPath + FString::FromInt(i) + TEXT(".mp3");
// EncodeVideoInfo.ClipStartFrame = TempClipData.ClipStartFrame;
@ -463,14 +469,65 @@ TArray<FEncodeVideoInfo> FUtils::TrackEncodeAudio(const FTrackData& TrackData, c
// }
// i++;
// }
return EncodeVideoInfos;
EncodeVideoInfo.EncodedVideoTimeCode = FGlobalData::GetTimeData(SavedClipData.ClipStartFrame);
EncodeVideoInfo.EncodedVideoName = ExportPath + TEXT(".mp3");
EncodeVideoInfo.ClipStartFrame = SavedClipData.ClipStartFrame;
EncodeVideoInfo.ClipEndFrame = SavedClipData.ClipEndFrame;
EncodeVideoInfo.TrackData = TrackData;
EncodeVideoInfo.ClipData = SavedClipData;
return EncodeVideoInfo;
}
TArray<FEncodeVideoInfo> FUtils::CombineAudio(const FEncodeVideoInfo& EncodeVideoInfo,
const FEncodeVideoInfo& EncodeAudioInfo, const FString& ExportPath)
FEncodeVideoInfo FUtils::CombineAudio(const FEncodeVideoInfo& LeftEncodeData,
const FEncodeVideoInfo& RightEncodeData, const FString& ExportPath)
{
return {};
FString LeftPathName = "";
FString RightPathName = "";
if (FPaths::FileExists(LeftEncodeData.EncodedVideoName))
{
LeftPathName = LeftEncodeData.EncodedVideoName;
}
if (FPaths::FileExists(RightEncodeData.EncodedVideoName))
{
RightPathName = RightEncodeData.EncodedVideoName;
}
FString Command = "";
if (LeftPathName == "")
{
Command = FString() + " -i " + "\"" +
RightPathName + "\"" + " -y -af \"pan=stereo|c0=0|c1=0\" " + "\"" +
ExportPath + ".mp3"+ "\"";
}
else if (RightPathName == "")
{
Command = FString() + " -i " + "\"" +
LeftPathName + "\"" + " -y -af \"pan=stereo|c0=c0\" " + "\"" +
ExportPath + ".mp3"+ "\"";
}
else
{
Command = FString() + " -i " + "\"" +
LeftPathName + "\"" + " -i " + "\"" +
RightPathName + "\"" +
" -y -filter_complex \"[0:a]pan=1c|c0=c0[a];[1:a]pan=1c|c0=c1[b];[a][b]amerge=inputs=2[aout]\" " +
"-map \"[aout]\" " +
ExportPath+ ".mp3" + "\"";
}
FProcHandle ProcHandle = FPlatformProcess::CreateProc(*GetFfmepg(), *Command, true, false, false, nullptr, 0, nullptr, nullptr);
while (FPlatformProcess::IsProcRunning(ProcHandle)){ FPlatformProcess::Sleep(0.1);};
FEncodeVideoInfo VideoInfo;
VideoInfo.EncodedVideoName = ExportPath + TEXT(".mp3");
VideoInfo.EncodedVideoTimeCode = FGlobalData::GetTimeData(LeftEncodeData.ClipStartFrame);
VideoInfo.ClipStartFrame = LeftEncodeData.ClipStartFrame;
VideoInfo.ClipData = LeftEncodeData.ClipData;
return VideoInfo;
}
FString FUtils::GetFfmepg()
@ -759,6 +816,10 @@ FString FUtils::Color2Hex3(FColor Color)
return FString::Printf(TEXT("%02X%02X%02X"), Color.R, Color.G, Color.B);
}
void FUtils::AddTips(TSharedPtr<SWidget> Widget)
{
GEngine->GameViewport->AddViewportWidgetContent(Widget.ToSharedRef());
}
FSaveModifier::FSaveModifier(const FString& FullSavedPath)

View File

@ -33,8 +33,8 @@ public:
static void CreateDefaultTimelineSave(const FString& SavedPath, const FTimelineInfo::ETimelineType Type);
static TArray<FEncodeVideoInfo> TrackEncodeVideo(const FTrackData& TrackData, const FString& ExportPath);
static TArray<FEncodeVideoInfo> TrackEncodeAudio(const FTrackData& TrackDataLeft, const FString& ExportPath);
static TArray<FEncodeVideoInfo> CombineAudio(const FEncodeVideoInfo& EncodeVideoInfo, const FEncodeVideoInfo& EncodeAudioInfo, const FString& ExportPath);
static FEncodeVideoInfo TrackEncodeAudio(const FTrackData& TrackDataLeft, const FString& ExportPath);
static FEncodeVideoInfo CombineAudio(const FEncodeVideoInfo& LeftEncodeData, const FEncodeVideoInfo& RightEncodeData, const FString& ExportPath);
static FString CurtainFullPath(const FString& GroupName)
{
// GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FGlobalData::BasePath);
@ -88,6 +88,10 @@ public:
static FString GetMsFromString(FString TimeString);
static FString Color2Hex3(FColor Color);
static void AddTips(TSharedPtr<SWidget> Widget);
};
template <typename T, typename U>
@ -140,7 +144,6 @@ T* FUtils::CastTypeByFormat(U* InValue, AVSampleFormat* Format)
}
class FSaveModifier
{
public:

View File

@ -8,6 +8,7 @@
#include "Brushes/SlateBoxBrush.h"
#include "Brushes/SlateImageBrush.h"
#include "Cut5/Utils/Utils.h"
#include "Cut5/Widgets/SCutMainWindow.h"
#include "Cut5/Widgets/Style/CutButtonWidgetStyle.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
@ -124,6 +125,8 @@ FReply SCurtain::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEv
TSharedPtr<FCurtainDragDrop> SCurtain::OpenThis()
{
CurtainPanel->MainWidgetInterface->GetSelf()->DeselectAll();
CurtainPanel->DeSelectedAll();
Curtain->bIsActive = true;
CurtainPanel->CallRender();

View File

@ -263,7 +263,16 @@ void SCurtainPanel::AddNewCurtain(int32 Index)
{
if (Index < Groups.Num())
{
Groups[Index].Curtains.Add(FCurtain());
FCurtain Curtain;
FUtils::CreateDefaultTimelineSave(FUtils::CurtainFullPath(Curtain.CurtainUUID.ToString()), FTimelineInfo::ETimelineType::FX);
{
FSaveModifier SaveModifier(FUtils::CurtainFullPath(Curtain.CurtainUUID.ToString()));
SaveModifier.TimelineInfo.CurrentOpenFullPath = FUtils::CurtainFullPath(Curtain.CurtainUUID.ToString());
SaveModifier.TimelineInfo.CurrentOpenType = FTimelineInfo::ETimelineType::FX;
Curtain.TimelineInfo = SaveModifier.TimelineInfo;
}
Groups[Index].Curtains.Add(Curtain);
CallRender();
}
}

View File

@ -335,6 +335,8 @@ struct CUT5_API FClipData : public TSharedFromThis<FClipData>
Ar << ClipData.VolumeData;
Ar << ClipData.bIsCycle;
Ar << ClipData.AudioCurtains;
Ar << ClipData.bIsVirtual;
Ar << ClipData.VirtualCurtainName;
return Ar;
};
@ -393,6 +395,12 @@ struct CUT5_API FClipData : public TSharedFromThis<FClipData>
}
}
}
void Move(int32 StartFrame)
{
const int32 OriginStartFrame = ClipStartFrame;
ClipStartFrame = StartFrame;
ClipEndFrame = ClipEndFrame + (OriginStartFrame - StartFrame);
}
void CropClip(ECropMethod CropMethod, int32 CropFrame)
{
if (CropMethod == ECropMethod::FromFront)
@ -427,27 +435,35 @@ struct CUT5_API FClipData : public TSharedFromThis<FClipData>
FTimelinePropertyData* ResourcePropertyDataPtr = nullptr;
FGuid ResourcePropertyGuid;
AVSampleFormat SampleFormat;
// Movies
FString MoviePath;
int32 VideoStartFrame = 0;
int32 VideoEndFrame = 0;
cv::VideoCapture* VideoCapture;
// Light Array
TArray<TArray<FColor>> LightArrayData;
// Player
FString PlayerName;
TArray<FColor> PlayerLightData;
EPresetType PresetType = EPresetType::NotAPresets;
bool bIsVirtual = false;
FString VirtualCurtainName = "";
// Placeholder
int32 PlaceHolder1 = 0;
int32 PlaceHolder2 = 0;
int32 PlaceHolder3 = 0;
int32 PlaceHolder4 = 0;
float PlaceHolder5 = 0;
float PlaceHolder6 = 0;
float PlaceHolder7 = 0;
float PlaceHolder8 = 0;
bool operator == (const FClipData& Other) const
{
return ClipGuid == Other.ClipGuid;

View File

@ -805,8 +805,6 @@ void DragDropOperator::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent&
NewClipData.MoviePath = ClipDragOperation.TimelinePropertyData->MoviePath;
NewClipData.ClipEndFrame = NewClipData.ClipStartFrame + ClipDragOperation.TimelinePropertyData->MovieFrameLength;
NewClipData.VideoEndFrame = ClipDragOperation.TimelinePropertyData->MovieFrameLength;
NewClipData.VideoCapture = ClipDragOperation.VideoCapture;
NewClipData.MovieBrushes = FFFMPEGUtils::GetMovieBrush(&NewClipData);
@ -861,6 +859,7 @@ void DragDropOperator::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent&
{
return;
}
TSharedPtr<STrackHead> TrackHead = StaticCastSharedPtr<STrackBody>(ClipDragOperation.OverrideWidget)->TrackHead;
static_cast<SCutMainWindow*>(TrackHead->MainWidgetInterface)->bRenderLine = false;
@ -912,6 +911,7 @@ void DragDropOperator::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent&
TrackHead->TrackData.ClipData.Add(NewClipData);
UpdateClipProcess(TrackBody->MainWidgetInterface, NewClipData);
SavedMainInterface->UpdateProperties(nullptr);
TrackBody->CallRender();
return;

View File

@ -81,6 +81,8 @@ void SEffectCard::Construct(const FArguments& InArgs)
PropertiesInterfaceGUID = CardProperty->Guid;
MainInterface->CurrentSelectedPropertiesInterfaceGuid = CardProperty->Guid;
MainInterface->UpdateProperties(this);
MainInterface->GetSelf()->DeselectAll();
MainInterface->OnSelectCard(CardProperty->Guid);
TSharedPtr<FEffectCardDragDrop> EffectCardDragDrop = MakeShared<FEffectCardDragDrop>();

View File

@ -390,6 +390,7 @@ void SEffectCardGroup::OnSelect()
MainInterface->OpenTimeline(Name, true);
MainInterface->CurrentSelectedPropertiesInterfaceGuid = EffectCardGroup->Guid;
MainInterface->UpdateProperties(this);
MainInterface->GetSelf()->DeselectAll();
MainInterface->OnSelectCard(EffectCardGroup->Guid);
}
}

View File

@ -12,7 +12,7 @@ void STips::Construct(const FArguments& InArgs)
{
OnEnsure = InArgs._OnEnsure;
FTextBlockStyle NormalText = FAppStyle::GetWidgetStyle<FTextBlockStyle>("NormalText");
NormalText.SetFontSize(20);
NormalText.SetFontSize(18);
ChildSlot
[
SNew(SOverlay)
@ -38,7 +38,7 @@ void STips::Construct(const FArguments& InArgs)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.Padding(0, 30, 0, 0)
.Padding(0, 20, 0, 0)
.HAlign(HAlign_Center)
.VAlign(VAlign_Top)
[
@ -47,7 +47,7 @@ void STips::Construct(const FArguments& InArgs)
.TextStyle(&NormalText)
]
+ SVerticalBox::Slot()
.Padding(0, 16, 0, 0)
.Padding(0, 5, 0, 0)
.HAlign(HAlign_Center)
.VAlign(VAlign_Top)
[
@ -109,7 +109,7 @@ void STips::Construct(const FArguments& InArgs)
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("确定")))
.ColorAndOpacity(FSlateColor(FLinearColor(1.0f, 1.0f, 1.0f, 1.0f)))
.ColorAndOpacity(FSlateColor(FLinearColor(0.6f, 0.6f, 0.7f, 1.0f)))
]
]
]

View File

@ -964,7 +964,7 @@ void SCutMainWindow::ExportProject(const FString& ExportPath)
return;
FGlobalData::ExportPath = ExportPath / FGlobalData::CurrentProjectName;
FPlatformFileManager::Get().GetPlatformFile().DeleteDirectoryRecursively(*ExportPath);
FPlatformFileManager::Get().GetPlatformFile().DeleteDirectoryRecursively(*FGlobalData::ExportPath);
IDList.Empty();
FPlatformFileManager::Get().GetPlatformFile().CreateDirectoryTree(*FGlobalData::ExportPath);
@ -1874,22 +1874,24 @@ tinyxml2::XMLElement* SCutMainWindow::GetVideoElement(tinyxml2::XMLElement* Pare
tinyxml2::XMLElement* SCutMainWindow::GetSoundElement(tinyxml2::XMLElement* Parent, FEncodeVideoInfo EncodeVideoInfo)
{
FTrackData LeftTrackData;
FTrackData RightTrackData;
int32 TempSoundID = 0;
for (FSingleTrackGroupInstance& SingleTrackGroupInstance : CutTimeline->TrackGroupInstances)
{
const TSharedPtr<STrackHead> TempTrackHead = StaticCastSharedPtr<STrackHead>(SingleTrackGroupInstance.Head);
if (TempTrackHead->TrackData.TrackType != ETrackType::AudioTrackR)
if (TempTrackHead->TrackData.TrackType == ETrackType::AudioTrack)
LeftTrackData = TempTrackHead->TrackData;
if (TempTrackHead->TrackData.TrackType == ETrackType::AudioTrackR)
{
continue;
}
if (GetTrackID(TempTrackHead->TrackData.DeviceTrack.Guid) != -1)
{
TempSoundID = GetTrackID(TempTrackHead->TrackData.DeviceTrack.Guid);
if (GetTrackID(TempTrackHead->TrackData.DeviceTrack.Guid) != -1)
{
TempSoundID = GetTrackID(TempTrackHead->TrackData.DeviceTrack.Guid);
}
RightTrackData = TempTrackHead->TrackData;
}
}
tinyxml2::XMLElement* Sound = Parent->InsertNewChildElement("Sound");
{
@ -1984,8 +1986,9 @@ tinyxml2::XMLElement* SCutMainWindow::GetVideoListElement(tinyxml2::XMLElement*
}
}
FString NewExportFilePath = FGlobalData::ExportPath / "Video" / Filename;
GEngine->AddOnScreenDebugMessage(-1, 10.0F, FColor::White, NewExportFilePath);
TArray<FEncodeVideoInfo> EncodeVideoInfos = FUtils::TrackEncodeVideo(StaticCastSharedPtr<STrackHead>(CutTimeline->TrackGroupInstances[i].Head)->TrackData, NewExportFilePath);
// GEngine->AddOnScreenDebugMessage(-1, 10.0F, FColor::White, NewExportFilePath);
TArray<FEncodeVideoInfo> EncodeVideoInfos =
FUtils::TrackEncodeVideo(StaticCastSharedPtr<STrackHead>(CutTimeline->TrackGroupInstances[i].Head)->TrackData, NewExportFilePath);
for (const FEncodeVideoInfo EncodeVideoInfo : EncodeVideoInfos)
{
GetVideoElement(VideoList, EncodeVideoInfo);
@ -2005,69 +2008,76 @@ tinyxml2::XMLElement* SCutMainWindow::GetSoundListElement(tinyxml2::XMLElement*
tinyxml2::XMLElement* AudioList = Parent->InsertNewChildElement("SoundList");
{
int32 Count = 0;
bool bGlobalAudio = false;
for (int32 i = 0; i < CutTimeline->TrackGroupInstances.Num(); i++)
FTrackData LeftTrackData;
FTrackData RightTrackData;
for (FSingleTrackGroupInstance& SingleTrackGroupInstance : CutTimeline->TrackGroupInstances)
{
const FTrackData& TrackData = StaticCastSharedPtr<STrackHead>(CutTimeline->TrackGroupInstances[i].Head)->TrackData;
if (StaticCastSharedPtr<STrackHead>(CutTimeline->TrackGroupInstances[i].Head)->TrackData.TrackType == ETrackType::AudioTrack ||
StaticCastSharedPtr<STrackHead>(CutTimeline->TrackGroupInstances[i].Head)->TrackData.TrackType == ETrackType::AudioTrackR)
const TSharedPtr<STrackHead> TempTrackHead = StaticCastSharedPtr<STrackHead>(SingleTrackGroupInstance.Head);
if (TempTrackHead->TrackData.TrackType == ETrackType::AudioTrack)
LeftTrackData = TempTrackHead->TrackData;
if (TempTrackHead->TrackData.TrackType == ETrackType::AudioTrackR)
RightTrackData = TempTrackHead->TrackData;
}
TArray<FClipData> SavedClipData = LeftTrackData.ClipData;
if (SavedClipData.Num() > 0)
{
Sort(SavedClipData.GetData(), SavedClipData.Num(), [](const FClipData& ClipDataA, const FClipData& ClipDataB)
{
FString Filename = GetCurrentSelectFileName();
if (StaticCastSharedPtr<STrackHead>(CutTimeline->TrackGroupInstances[i].Head)->TrackData.TrackType == ETrackType::AudioTrackR)
{
Filename += TEXT("_R");
}
if (StaticCastSharedPtr<STrackHead>(CutTimeline->TrackGroupInstances[i].Head)->TrackData.TrackType == ETrackType::AudioTrack)
{
Filename += TEXT("_L");
}
FString NewExportFilePath = FGlobalData::ExportPath / "Sound" / Filename;
return ClipDataA.ClipStartFrame < ClipDataB.ClipStartFrame;
});
if (SavedClipData[0].ClipStartFrame != 0 && SavedClipData[0].bIsVirtual == false)
{
GetSoundElement(AudioList, {});
}
}
bGlobalAudio = false;
FEncodeVideoInfo GlobalAudioEncodeVideoInfo = {};
for (const FEncodeVideoInfo& EncodeVideoInfo : AllGlobalSounds)
bool bIsVirtual = false;
for (FClipData& ClipData : LeftTrackData.ClipData)
{
if (ClipData.bIsVirtual)
{
bIsVirtual = true;
const FString VirtualFileName = FGlobalData::ExportPath / "Sound" / ClipData.VirtualCurtainName;
FEncodeVideoInfo EncodeVideoInfo;
EncodeVideoInfo.EncodedVideoName = VirtualFileName;
GetSoundElement(AudioList, EncodeVideoInfo);
}
}
if (!bIsVirtual)
{
for (FClipData& ClipData : RightTrackData.ClipData)
{
if (ClipData.bIsVirtual)
{
if (EncodeVideoInfo.ClipData.AudioCurtains.Contains(GetCurrentSelectCurtain()))
{
bGlobalAudio = true;
GlobalAudioEncodeVideoInfo = EncodeVideoInfo;
Count++;
};
}
if (bGlobalAudio == false)
{
TArray<FEncodeVideoInfo> EncodeVideoInfos = FUtils::TrackEncodeAudio(StaticCastSharedPtr<STrackHead>(CutTimeline->TrackGroupInstances[i].Head)->TrackData, NewExportFilePath);
for (const FEncodeVideoInfo EncodeVideoInfo : EncodeVideoInfos)
{
if (EncodeVideoInfo.ClipData.AudioCurtains.Num() > 0)
{
AllGlobalSounds.Add(EncodeVideoInfo);
}
}
for (const FEncodeVideoInfo EncodeVideoInfo : EncodeVideoInfos)
{
GetSoundElement(AudioList, EncodeVideoInfo);
Count++;
}
}
else
{
GetSoundElement(AudioList, GlobalAudioEncodeVideoInfo);
bIsVirtual = true;
const FString VirtualFileName = FGlobalData::ExportPath / "Sound" / ClipData.VirtualCurtainName;
FEncodeVideoInfo EncodeVideoInfo;
EncodeVideoInfo.EncodedVideoName = VirtualFileName;
GetSoundElement(AudioList, EncodeVideoInfo);
}
}
}
if (Count == 0 && !bGlobalAudio)
if (!bIsVirtual)
{
GetSoundElement(AudioList, FEncodeVideoInfo());
const FString LeftFilename = GetCurrentSelectFileName() + TEXT("_L");
const FString RightFilename = GetCurrentSelectFileName() + TEXT("_R");
const FString LeftNewExportFilePath = FUtils::GetProjectTempPath() / "Sound" / LeftFilename;
const FString RightNewExportFilePath = FUtils::GetProjectTempPath() / "Sound" / RightFilename;
const FEncodeVideoInfo LeftEncodeVideoInfos = FUtils::TrackEncodeAudio(LeftTrackData, LeftNewExportFilePath);
const FEncodeVideoInfo RightEncodeVideoInfos = FUtils::TrackEncodeAudio(RightTrackData, RightNewExportFilePath);
const FString ExportFilename = FGlobalData::ExportPath / "Sound" / GetCurrentSelectFileName();
const FEncodeVideoInfo End = FUtils::CombineAudio(LeftEncodeVideoInfos, RightEncodeVideoInfos, ExportFilename);
GetSoundElement(AudioList, End);
}
}
return nullptr;
}
@ -2391,6 +2401,21 @@ FGuid SCutMainWindow::GetCurrentSelectCurtain() const
return FGuid();
}
FTimelineInfo SCutMainWindow::GetCurrentSelectCurtainTimelineInfo() const
{
for (FCurtainGroup& CurtainGroup : CurtainPanel->Groups)
{
for (const FCurtain& Curtain : CurtainGroup.Curtains)
{
if (Curtain.bIsActive)
{
return Curtain.TimelineInfo;
}
}
}
return FTimelineInfo();
}
bool SCutMainWindow::IsSelectCurtain() const
{
for (FCurtainGroup& CurtainGroup : CurtainPanel->Groups)
@ -2425,6 +2450,9 @@ void SCutMainWindow::DeselectAll()
}
}
}
CurtainPanel->CallRender();
EffectCardsPanel->CallRender();
}
int32 SCutMainWindow::GetTrackID(FGuid Guid) const

View File

@ -125,7 +125,10 @@ public:
tinyxml2::XMLElement* GetFlashLight(tinyxml2::XMLElement* Parent, const FClipData& ClipData);
tinyxml2::XMLElement* GetGradientLight(tinyxml2::XMLElement* Parent, const FClipData& ClipData);
FString GetCurrentSelectFileName() const;
FGuid GetCurrentSelectCurtain() const;
FTimelineInfo GetCurrentSelectCurtainTimelineInfo() const;
bool IsSelectCurtain() const;
void DeselectAll();

View File

@ -62,6 +62,7 @@ FReply STimelineClip::OnBorderMouseButtonDown(const FGeometry& Geometry, const F
MenuBuilder.AddMenuEntry(FTimelineClipCommands::Get().AddVolumeHere);
}
if (ClipData->ClipType != ETrackType::ProjectorTrack)
{
if (ClipData->bIsCycle)
@ -84,6 +85,11 @@ FReply STimelineClip::OnBorderMouseButtonDown(const FGeometry& Geometry, const F
return FReply::Handled();
}
if (ClipData->bIsVirtual)
return FReply::Handled();
TSharedPtr<FClipProxy> ClipProxy = FClipProxy::GetProxy();
ClipProxy->UpdateInterface(SharedThis(this));
@ -645,6 +651,10 @@ int32 STimelineClip::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGe
{
FSlateDrawElement::MakeText(OutDrawElements, LayerId + 9, AllottedGeometry.ToPaintGeometry(), FText::FromString(TEXT("循环播放")), FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("NormalText").Font, ESlateDrawEffect::None, FLinearColor::White);
}
if (ClipData->bIsVirtual == true)
{
FSlateDrawElement::MakeText(OutDrawElements, LayerId + 9, AllottedGeometry.ToPaintGeometry(), FText::FromString(TEXT("虚拟轨道(不可编辑)")), FAppStyle::Get().GetWidgetStyle<FTextBlockStyle>("NormalText").Font, ESlateDrawEffect::None, FLinearColor::White);
}
if (MainWidgetInterface->GetCutTimeline()->SelectedClips.Contains(ClipData->ClipGuid))

View File

@ -0,0 +1,71 @@
#include "TimelineLoader.h"
FTimelineLoader::FTimelineLoader(const FString& LoadPath, bool bNeedSaveWhenExit)
{
this->bNeedSaveWhenExit = bNeedSaveWhenExit;
this->LoadPath = FPaths::ConvertRelativePathToFull(LoadPath);
TArray<uint8> LoadData;
const bool Response = FFileHelper::LoadFileToArray(LoadData, *this->LoadPath);
if (LoadData.Num() == 0 || !Response)
{
return;
}
FMemoryReader MemoryReader(LoadData, false);
MemoryReader << TimelineInfo;
int32 ClipCount = 0;
MemoryReader << ClipCount;
MemoryReader << ClipData;
}
FTimelineLoader::~FTimelineLoader()
{
if (bNeedSaveWhenExit)
{
TArray<uint8> SaveData;
FMemoryWriter MemoryWriter(SaveData, false);
int32 ClipCount = ClipData.Num();
MemoryWriter << TimelineInfo;
MemoryWriter << ClipCount;
MemoryWriter << ClipData;
FFileHelper::SaveArrayToFile(SaveData, *LoadPath);
}
}
TArray<FClipData> FTimelineLoader::GetSpecifyClipData(const FTrackData& TrackData) const
{
TArray<FClipData> ResultClipData;
for (const FClipData& Clip : ClipData)
{
if (Clip.BindTrackGuid == TrackData.DeviceTrack.Guid)
{
ResultClipData.Add(Clip);
}
}
return ResultClipData;
}
TArray<FClipData> FTimelineLoader::GetSpecifyClipData(const ETrackType TrackType) const
{
TArray<FClipData> ResultClipData;
for (const FClipData& Clip : ClipData)
{
if (Clip.ClipType == TrackType)
{
ResultClipData.Add(Clip);
}
}
return ResultClipData;
}
void FTimelineLoader::RemoveClipData(int32 Index)
{
ClipData.RemoveAt(Index);
}
void FTimelineLoader::RemoveClipData(const FClipData& RemoveClipData)
{
ClipData.Remove(RemoveClipData);
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "Cut5/Widgets/DefineGlobal.h"
class FTimelineLoader
{
private:
FString Version = "1.0.1";
bool bNeedSaveWhenExit = false;
FString LoadPath = "";
FTimelineInfo TimelineInfo;
TArray<FClipData> ClipData;
public:
explicit FTimelineLoader(const FString& LoadPath, bool bNeedSaveWhenExit = false);
~FTimelineLoader();
TArray<FClipData> GetSpecifyClipData(const FTrackData& TrackData) const;
TArray<FClipData> GetSpecifyClipData(const ETrackType TrackType) const;
TArray<FClipData>& GetClipData() { return ClipData; }
void RemoveClipData(int32 Index = 0);
void RemoveClipData(const FClipData& RemoveClipData);
FTimelineInfo& GetTimelineInfo() { return TimelineInfo; }
};

View File

@ -5,6 +5,8 @@
#include "Cut5/Widgets/SCutTimeline.h"
#include "Cut5/Widgets/STimelineClip.h"
#include "Cut5/Widgets/MicroWidgets/SNewProjectTips.h"
#include "Cut5/Widgets/MicroWidgets/STips.h"
#include "Cut5/Widgets/SaveConverter/Timeline/TimelineLoader.h"
#include "Widgets/Input/SSpinBox.h"
#include "Widgets/Layout/SSpacer.h"
@ -331,15 +333,19 @@ TSharedPtr<SWidget> FClipProxy::GetPropertiesWidget()
];
}
if (ClipData->ClipType == ETrackType::AudioTrack || ClipData->ClipType == ETrackType::AudioTrackR)
if ((ClipData->ClipType == ETrackType::AudioTrack || ClipData->ClipType == ETrackType::AudioTrackR) && MainInterface->GetSelf()->GetCurrentSelectCurtain() != FGuid())
{
AudiosCurtainOptions.Empty();
TArray<FCurtainGroup>& Groups = MainInterface->GetSelf()->CurtainPanel->Groups;
for (FCurtainGroup& Group : Groups)
{
for (FCurtain& Curtain : Group.Curtains)
{
AudiosCurtainOptions.Add(MakeShared<FStringWithGUID>(Curtain.CurtainName, Curtain.CurtainUUID));
if (Curtain.CurtainUUID != MainInterface->GetSelf()->GetCurrentSelectCurtain())
{
AudiosCurtainOptions.Add(MakeShared<FStringWithGUID>(Curtain.CurtainName, Curtain.CurtainUUID));
}
}
}
@ -387,18 +393,84 @@ TSharedPtr<SWidget> FClipProxy::GetPropertiesWidget()
.IsChecked(ClipData->AudioCurtains.Contains(*InItem.Get()) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked)
.OnCheckStateChanged_Lambda([this, InItem](const ECheckBoxState& State)
{
if (State == ECheckBoxState::Checked)
const FGuid& Guid = InItem.Get()->Guid;
FCurtainGroup* Group = nullptr;
const FString CurrentPath = MainInterface->GetSelf()->CurtainPanel->FindCurtain(Guid,Group)->TimelineInfo.CurrentOpenFullPath;
FTimelineLoader TimelineLoader(CurrentPath, true);
if (TimelineLoader.GetSpecifyClipData(ClipData->ClipType).Num() > 0 && State == ECheckBoxState::Checked)
{
ClipData->AudioCurtains.Add(*InItem.Get());
MainInterface->UpdateProperties(this);
FUtils::AddTips(
SNew(STips)
.Title(TEXT("警告"))
.SubTitle(TEXT("选中的幕中的音频轨道已经存在音频片段\n如果继续添加,将会覆盖原有的音频片段\n是否继续?"))
.OnEnsure_Lambda([this, State, InItem, CurrentPath](const FString& String)
{
// 先清空
FTimelineLoader ModifyTimelineLoader(CurrentPath, true);
TArray<FClipData> Clips = ModifyTimelineLoader.GetSpecifyClipData(ClipData->ClipType);
for (int32 i = Clips.Num() - 1; i >= 0; i--)
{
ModifyTimelineLoader.RemoveClipData(Clips[i]);
}
FClipData NewClip = *ClipData;
NewClip.Move(0);
NewClip.bIsVirtual = true;
NewClip.VirtualCurtainName = MainInterface->GetSelf()->GetCurrentSelectFileName();
NewClip.ClipGuid = FGuid::NewGuid();
ModifyTimelineLoader.GetClipData().Add(NewClip);
if (State == ECheckBoxState::Checked)
{
ClipData->AudioCurtains.Add(*InItem.Get());
MainInterface->UpdateProperties(this);
}
else
{
ClipData->AudioCurtains.Remove(*InItem.Get());
MainInterface->UpdateProperties(this);
}
})
);
}
else
{
ClipData->AudioCurtains.Remove(*InItem.Get());
MainInterface->UpdateProperties(this);
if (State == ECheckBoxState::Checked)
{
FClipData NewClip = *ClipData;
NewClip.Move(0);
NewClip.bIsVirtual = true;
NewClip.VirtualCurtainName = MainInterface->GetSelf()->GetCurrentSelectFileName();
NewClip.ClipGuid = FGuid::NewGuid();
TimelineLoader.GetClipData().Add(NewClip);
ClipData->AudioCurtains.Add(*InItem.Get());
MainInterface->UpdateProperties(this);
}
else
{
if (TimelineLoader.GetSpecifyClipData(ClipData->ClipType).Num() > 0)
{
TArray<FClipData> Clips = TimelineLoader.GetSpecifyClipData(ClipData->ClipType);
for (int32 i = Clips.Num() - 1; i >= 0; i--)
{
TimelineLoader.RemoveClipData(Clips[i]);
}
}
ClipData->AudioCurtains.Remove(*InItem.Get());
MainInterface->UpdateProperties(this);
}
}
})
];

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB