Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion exporter/src/export/PAGExport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ std::shared_ptr<pag::File> PAGExport::exportAsFile() {

auto compositions = session->compositions;
if (session->exportAudio && session->configParam.isTagCodeSupport(pag::TagCode::AudioBytes)) {
GetAudioSequence(itemHandle, session->outputPath, compositions[compositions.size() - 1]);
GetAudioSequence(itemHandle, compositions[compositions.size() - 1]);
CombineAudioMarkers(compositions);
}
if (session->stopExport) {
Expand Down
22 changes: 10 additions & 12 deletions exporter/src/export/sequence/AudioSequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include "AudioSequence.h"
#include <cstdio>
#include "ffmovie/movie.h"
#include "platform/PlatformHelper.h"
#include "utils/FileHelper.h"
#include "utils/PAGExportSession.h"
#include "utils/PAGExportSessionManager.h"

Expand All @@ -40,9 +42,11 @@ void CombineAudioMarkers(std::vector<pag::Composition*>& compositions) {
}
}

static std::string EncodeAudioToMP4(int16_t* samples, int numSamples, int channels, int sampleRate,
const std::string& outputPath) {
std::string mp4Path = outputPath + "_audio.mp4";
static std::string EncodeAudioToMP4(int16_t* samples, int numSamples, int channels,
int sampleRate) {
// Use a safe ASCII path in the temp folder to avoid encoding issues with non-ASCII characters
// on Windows, where FFmpeg may not correctly handle UTF-8 paths.
std::string mp4Path = JoinPaths(GetTempFolderPath(), ".pag_audio_temp.mp4");
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Medium] 两个问题:

  1. 临时文件名固定为 .pag_audio_temp.mp4,多进程并发导出时会互相覆盖。建议使用唯一文件名(如添加 PID 或时间戳后缀)。
  2. 多个提前返回路径(encoder/muxer 创建失败等)不会删除已创建的临时文件。建议使用 RAII 或 scope guard 统一清理。

ffmovie::AudioExportConfig audioConfig = {};
audioConfig.sampleRate = sampleRate;
audioConfig.channels = channels;
Expand Down Expand Up @@ -106,16 +110,9 @@ static std::string EncodeAudioToMP4(int16_t* samples, int numSamples, int channe
return mp4Path;
}

void GetAudioSequence(const AEGP_ItemH& itemHandle, const std::string& outputPath,
pag::Composition* composition) {
void GetAudioSequence(const AEGP_ItemH& itemHandle, pag::Composition* composition) {
const auto& Suites = GetSuites();
const auto& PluginID = GetPluginID();
std::string path = outputPath;
const std::string pagExtension = ".pag";
if (path.size() > pagExtension.size() &&
path.compare(path.size() - pagExtension.size(), pagExtension.size(), pagExtension) == 0) {
path = path.substr(0, path.size() - pagExtension.size());
}

AEGP_ItemFlags flags = 0;
Suites->ItemSuite6()->AEGP_GetItemFlags(itemHandle, &flags);
Expand Down Expand Up @@ -149,12 +146,13 @@ void GetAudioSequence(const AEGP_ItemH& itemHandle, const std::string& outputPat
int offset = startSamples * soundFormat2.num_channelsL;
std::string mp4Path = EncodeAudioToMP4(static_cast<int16_t*>(samples) + offset, numSamples,
static_cast<int>(soundFormat2.num_channelsL),
static_cast<int>(soundFormat2.sample_rateF), path);
static_cast<int>(soundFormat2.sample_rateF));

if (!mp4Path.empty()) {
composition->audioBytes = pag::ByteData::FromPath(mp4Path).release();
composition->audioStartTime = static_cast<pag::Frame>(
(startSamples / soundFormat2.sample_rateF) * composition->frameRate);
DeleteFile(mp4Path);
} else {
PAGExportSessionManager::GetInstance()->recordWarning(AlertInfoType::AudioEncodeFail);
}
Expand Down
3 changes: 1 addition & 2 deletions exporter/src/export/sequence/AudioSequence.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ namespace exporter {

void CombineAudioMarkers(std::vector<pag::Composition*>& compositions);

void GetAudioSequence(const AEGP_ItemH& itemHandle, const std::string& outputPath,
pag::Composition* composition);
void GetAudioSequence(const AEGP_ItemH& itemHandle, pag::Composition* composition);

} // namespace exporter
1 change: 1 addition & 0 deletions viewer/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ assets/translation/Chinese.qm
build_*/
cmake-build-*/
src/editing/rttr/PAGRttr.hpp
src/editing/rttr/PAGXRttr.hpp
src/version.h
tools/*RttrAutoRegister*
tools/libclang.dll
2 changes: 2 additions & 0 deletions viewer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ endif ()
set(PAG_USE_QT ON)
set(PAG_USE_RTTR ON)
set(PAG_USE_LIBAVC OFF)
set(PAG_BUILD_PAGX ON)
set(PAG_BUILD_TESTS OFF)
set(PAG_USE_HARFBUZZ ON)
set(PAG_BUILD_SHARED OFF)
Expand All @@ -145,6 +146,7 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
list(APPEND PAG_OPTIONS "-DPAG_USE_QT=${PAG_USE_QT}")
list(APPEND PAG_OPTIONS "-DPAG_USE_RTTR=${PAG_USE_RTTR}")
list(APPEND PAG_OPTIONS "-DPAG_USE_LIBAVC=${PAG_USE_LIBAVC}")
list(APPEND PAG_OPTIONS "-DPAG_BUILD_PAGX=${PAG_BUILD_PAGX}")
list(APPEND PAG_OPTIONS "-DPAG_BUILD_TESTS=${PAG_BUILD_TESTS}")
list(APPEND PAG_OPTIONS "-DPAG_USE_HARFBUZZ=${PAG_USE_HARFBUZZ}")
list(APPEND PAG_OPTIONS "-DPAG_BUILD_SHARED=${PAG_BUILD_SHARED}")
Expand Down
51 changes: 33 additions & 18 deletions viewer/assets/qml/ControlForm.qml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import "components"

Item {
id: form
required property var pagView
property bool hasPAGFile: (pagView && pagView.filePath !== "")
required property var contentView
property bool hasPAGFile: (contentView && contentView.viewModel.filePath !== "")
property bool hasAnimation: (contentView && contentView.viewModel.hasAnimation)

property bool updateAvailable: false

Expand Down Expand Up @@ -49,7 +50,8 @@ Item {
anchors.leftMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
enabled: hasPAGFile
visible: hasAnimation
enabled: hasPAGFile && hasAnimation
background: Rectangle {
x: progressSlider.leftPadding
y: progressSlider.topPadding + progressSlider.availableHeight / 2 - height / 2
Expand Down Expand Up @@ -97,41 +99,46 @@ Item {
id: playButton
width: 48
height: 48
visible: true
enabled: hasPAGFile
enabled: hasPAGFile && hasAnimation
focusPolicy: Qt.NoFocus
flat: true
display: AbstractButton.IconOnly
opacity: pressed ? 1 : hovered ? 0.9 : 0.8
opacity: enabled ? (pressed ? 1 : hovered ? 0.9 : 0.8) : 0.3
background: Image {
source: ((pagView && pagView.isPlaying) || form.lastPlayStatusIsPlaying) ? "qrc:/images/pause.png" : "qrc:/images/play.png"
source: ((contentView && contentView.viewModel.isPlaying) || form.lastPlayStatusIsPlaying) ? "qrc:/images/pause.png" : "qrc:/images/play.png"
}
onClicked: {
pagView.isPlaying = !pagView.isPlaying;
if (contentView) {
contentView.viewModel.isPlaying = !contentView.viewModel.isPlaying;
}
}
}
Button {
id: previousButton
width: 24
height: 20
enabled: hasPAGFile
enabled: hasPAGFile && hasAnimation
focusPolicy: Qt.NoFocus
display: AbstractButton.IconOnly
flat: true
anchors.verticalCenter: parent.verticalCenter
opacity: pressed ? 1 : hovered ? 0.9 : 0.8
opacity: enabled ? (pressed ? 1 : hovered ? 0.9 : 0.8) : 0.3
background: Image {
source: "qrc:/images/previous.png"
}
onPressAndHold: {
pagView.previousFrame();
if (contentView) {
contentView.viewModel.previousFrame();
}
longPressPreTimer.start();
}
onReleased: {
longPressPreTimer.stop();
}
onClicked: {
pagView.previousFrame();
if (contentView) {
contentView.viewModel.previousFrame();
}
}

Timer {
Expand All @@ -140,32 +147,38 @@ Item {
repeat: true
running: false
onTriggered: {
pagView.previousFrame();
if (contentView) {
contentView.viewModel.previousFrame();
}
}
}
}
Button {
id: nextButton
width: 24
height: 20
enabled: hasPAGFile
enabled: hasPAGFile && hasAnimation
focusPolicy: Qt.NoFocus
display: AbstractButton.IconOnly
flat: true
anchors.verticalCenter: parent.verticalCenter
opacity: pressed ? 1 : hovered ? 0.9 : 0.8
opacity: enabled ? (pressed ? 1 : hovered ? 0.9 : 0.8) : 0.3
background: Image {
source: "qrc:/images/next.png"
}
onPressAndHold: {
pagView.nextFrame();
if (contentView) {
contentView.viewModel.nextFrame();
}
longPressNextTimer.start();
}
onReleased: {
longPressNextTimer.stop();
}
onClicked: {
pagView.nextFrame();
if (contentView) {
contentView.viewModel.nextFrame();
}
}

Timer {
Expand All @@ -174,7 +187,9 @@ Item {
repeat: true
running: false
onTriggered: {
pagView.nextFrame();
if (contentView) {
contentView.viewModel.nextFrame();
}
}
}
}
Expand Down
83 changes: 77 additions & 6 deletions viewer/assets/qml/FileTreeViewDelegate.qml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Item {
required property int layerIdKey
required property int markerIndexKey
required property bool isEditableKey
required property bool selected

implicitWidth: treeView.width
implicitHeight: 22
Expand Down Expand Up @@ -53,6 +54,7 @@ Item {
id: displayComponent

Row {
id: displayRow
anchors.fill: parent
Item {
width: viewDelegate.padding + (viewDelegate.depth * viewDelegate.indentation)
Expand All @@ -74,6 +76,7 @@ Item {
}

Text {
id: displayNameText
x: viewDelegate.padding + (viewDelegate.isTreeNode ? (viewDelegate.depth * viewDelegate.indentation + font.pixelSize) : 0)
height: viewDelegate.height
text: viewDelegate.name + " : "
Expand All @@ -87,16 +90,50 @@ Item {
}
Comment thread
CodeJhF marked this conversation as resolved.

Text {
id: displayValueText
width: displayRow.width - displayNameText.width - viewDelegate.padding - (viewDelegate.depth * viewDelegate.indentation) - 20
height: viewDelegate.height
text: viewDelegate.value.length > 20 ? viewDelegate.value.substr(0, 20) : viewDelegate.value
text: viewDelegate.value
font.pixelSize: 12
font.kerning: false
font.letterSpacing: 0.2
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
clip: true
textFormat: TextEdit.RichText
elide: Text.ElideRight
textFormat: Text.PlainText
color: viewDelegate.selected ? "#FFFFFF" : "#EEEEEE"

MouseArea {
id: displayValueArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
}

ToolTip {
id: displayToolTip
parent: displayValueText
visible: displayValueArea.containsMouse && displayValueText.truncated
delay: 500
x: 0
y: displayValueText.height + 2
width: Math.min(tooltipText1.implicitWidth + 16, 280)

contentItem: Text {
id: tooltipText1
text: viewDelegate.value
font.pixelSize: 12
color: "#EEEEEE"
wrapMode: Text.Wrap
}

background: Rectangle {
color: "#2D2D37"
border.color: "#3485F6"
border.width: 1
radius: 4
}
}
}
}
}
Expand All @@ -105,6 +142,7 @@ Item {
id: editComponent

Row {
id: editRow
anchors.fill: parent
Item {
width: viewDelegate.padding + (viewDelegate.depth * viewDelegate.indentation)
Expand Down Expand Up @@ -142,16 +180,49 @@ Item {

Text {
id: valueText
width: editRow.width - nameText.width - viewDelegate.padding - (viewDelegate.depth * viewDelegate.indentation) - 40
height: viewDelegate.height
text: viewDelegate.value.length > 20 ? viewDelegate.value.substr(0, 20) : viewDelegate.value
text: viewDelegate.value
font.pixelSize: 12
font.kerning: false
font.letterSpacing: 0.2
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
clip: true
textFormat: TextEdit.RichText
elide: Text.ElideRight
textFormat: Text.PlainText
color: viewDelegate.selected ? "#FFFFFF" : "#EEEEEE"

MouseArea {
id: editValueArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
}

ToolTip {
id: editToolTip
parent: valueText
visible: editValueArea.containsMouse && valueText.truncated
delay: 500
x: 0
y: valueText.height + 2
width: Math.min(tooltipText2.implicitWidth + 16, 280)

contentItem: Text {
id: tooltipText2
text: viewDelegate.value
font.pixelSize: 12
color: "#EEEEEE"
wrapMode: Text.Wrap
}

background: Rectangle {
color: "#2D2D37"
border.color: "#3485F6"
border.width: 1
radius: 4
}
}
}

ComboBox {
Expand Down
Loading
Loading