15 Commits

Author SHA1 Message Date
Dmitry Makarenko
40322b0650 Merge pull request #12 from kryksyh/bugfixes
Bugfixes
2026-01-27 12:29:36 +03:00
Dmitry Makarenko
edd0a14a52 Cleanup journal files on finish 2026-01-26 21:53:59 +03:00
Dmitry Makarenko
956b312035 Fix removeUnusedBlocks finding missing blocks instead of orphaned 2026-01-26 21:53:59 +03:00
Dmitry Makarenko
b5013399bc Fix Buffer::read corrupting data across chunk boundaries 2026-01-26 21:53:59 +03:00
Dmitry Makarenko
99837208db Fix IdsLookup::store out-of-bounds access on resize 2026-01-26 21:53:59 +03:00
Dmitry Makarenko
601208bb9c Fix bool conversion from string treating "0" as true 2026-01-26 21:53:59 +03:00
Dmitry Makarenko
cd7654c2ef Merge pull request #11 from kryksyh/bump-version
Bump version
2026-01-26 21:08:46 +03:00
Dmitry Makarenko
516c53fcf2 Bump tool version to 1.0.5 2026-01-26 20:40:46 +03:00
Dmitry Makarenko
e99cae9ce5 Bump supported project version to 3.7 2026-01-26 20:40:46 +03:00
Dmitry Makarenko
cce14f3260 Merge pull request #10 from kryksyh/add-release-workflow
Add release workflow
2026-01-26 20:13:14 +03:00
Dmitry Makarenko
24de66669e Merge pull request #9 from kryksyh/macos-support
MacOS support
2026-01-26 17:19:24 +03:00
Dmitry Makarenko
743e8e8615 Add release workflow 2026-01-26 16:36:34 +03:00
Dmitry Makarenko
b67cef0611 Update CI for Conan 2 and add macOS builds 2026-01-26 16:18:18 +03:00
Dmitry Makarenko
a080523fd8 Fix std::from_chars for floating-point types 2026-01-26 16:04:23 +03:00
Dmitry Makarenko
bb1a76604f Migrate from Conan 1 to Conan 2 2026-01-26 16:03:06 +03:00
13 changed files with 201 additions and 172 deletions

View File

@@ -6,10 +6,8 @@ on:
workflow_dispatch:
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
CONAN_USER_HOME: ${{ github.workspace }}/conan-home/
CONAN_USER_HOME_SHORT: "${{ github.workspace }}/conan-home/short"
jobs:
build:
name: Building ${{ matrix.config.os }}
@@ -18,58 +16,51 @@ jobs:
matrix:
config:
- os: ubuntu-latest
generator: Unix Makefiles
#- os: macos-11
# generator: Xcode
preset: conan-release
- os: macos-latest
preset: conan-release
- os: windows-2022
generator: Visual Studio 17 2022
preset: conan-default
steps:
- name: switch to gcc-11 on linux
if: ${{ matrix.config.os == 'ubuntu-latest' }}
run: |
sudo apt install gcc-11 g++-11
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 --slave /usr/bin/g++ g++ /usr/bin/g++-11 --slave /usr/bin/gcov gcov /usr/bin/gcov-11
sudo update-alternatives --set gcc /usr/bin/gcc-11
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v2.3.1
uses: actions/setup-python@v5
with:
cache: pip
#cache-dependency-path: # optional
- uses: BSFishy/pip-action@v1
python-version: '3.12'
- name: Install Conan
run: pip install "conan>=2"
- name: Detect Conan profile
run: conan profile detect --force
- name: Cache Conan packages
uses: actions/cache@v4
with:
packages: conan<2
- name: Cache for .conan
id: cache-conan
uses: actions/cache@v2
env:
cache-name: cache-conan-modules
with:
path: ${{ env.CONAN_USER_HOME }}
key: host-${{ matrix.config.os }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('CMakeLists.txt') }}
path: ~/.conan2
key: conan-${{ matrix.config.os }}-${{ hashFiles('conanfile.txt') }}
restore-keys: |
host-3-${{ matrix.config.os }}-
conan-${{ matrix.config.os }}-
- name: Install dependencies
run: conan install . --output-folder=build/${{ env.BUILD_TYPE }} --build=missing -s build_type=${{ env.BUILD_TYPE }}
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: cmake -B ${{github.workspace}}/build -G"${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} -DCMAKE_CONFIGURATION_TYPES=${{ env.BUILD_TYPE }}
run: cmake --preset ${{ matrix.config.preset }}
- name: Build
# Build your program with the given configuration
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
run: cmake --build build/${{ env.BUILD_TYPE }} --config ${{ env.BUILD_TYPE }}
- name: Package
# Build your program with the given configuration
run: |
cd build
cmake --build . --target package --config "${{ env.BUILD_TYPE }}"
working-directory: build/${{ env.BUILD_TYPE }}
run: cmake --build . --target package --config ${{ env.BUILD_TYPE }}
- name: Upload artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: audacity-project-tools-${{ matrix.config.os }}
path: |
build/packages/*
!build/packages/_CPack_Packages
build/${{ env.BUILD_TYPE }}/packages/*
!build/${{ env.BUILD_TYPE }}/packages/_CPack_Packages
if-no-files-found: error

53
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: Release
on:
workflow_dispatch:
inputs:
run_id:
description: 'Workflow run ID to take artifacts from'
required: true
type: string
tag:
description: 'Tag name (e.g. v1.0.5)'
required: true
type: string
prerelease:
description: 'Mark as pre-release'
required: false
type: boolean
default: false
jobs:
release:
name: Create Release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download artifacts
env:
GH_TOKEN: ${{ github.token }}
run: |
mkdir -p release-assets
gh run download ${{ inputs.run_id }} --dir release-artifacts
for dir in release-artifacts/*/; do
name=$(basename "$dir")
(cd "$dir" && zip -r "../../release-assets/${name}.zip" .)
done
- name: Create tag
run: |
git tag ${{ inputs.tag }}
git push origin ${{ inputs.tag }}
- name: Create release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create ${{ inputs.tag }} \
--title "${{ inputs.tag }}" \
${{ inputs.prerelease && '--prerelease' || '' }} \
release-assets/*

10
.gitignore vendored
View File

@@ -35,3 +35,13 @@ build/**
.vs/**
out/**
.venv/**
CMakeUserPresets.json
# Audacity files
*.aup3
*.aup4
*.aup3-shm
*.aup3-wal
*.aup4-shm
*.aup4-wal

View File

@@ -14,108 +14,18 @@ if( CMAKE_GENERATOR MATCHES "Visual Studio" )
)
endif()
list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR})
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR})
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/master/conan.cmake"
"${CMAKE_BINARY_DIR}/conan.cmake"
TLS_VERIFY ON)
endif()
include(${CMAKE_BINARY_DIR}/conan.cmake)
set(CMAKE_CXX_STANDARD 17)
if(WIN32)
add_definitions(-DUNICODE -D_UNICODE)
endif()
set(DEPENDENCIES
REQUIRES
fmt/7.1.3
sqlite3/3.40.0
sqlitecpp/3.1.1
gflags/2.2.2
utfcpp/3.2.1
boost/1.78.0
OPTIONS
boost:without_atomic=False
boost:without_chrono=True
boost:without_container=True
boost:without_context=True
boost:without_contract=True
boost:without_coroutine=True
boost:without_date_time=True
boost:without_exception=True
boost:without_fiber=True
boost:without_filesystem=False
boost:without_graph=True
boost:without_graph_parallel=True
boost:without_iostreams=True
boost:without_json=True
boost:without_locale=True
boost:without_log=True
boost:without_math=True
boost:without_mpi=True
boost:without_nowide=True
boost:without_program_options=True
boost:without_python=True
boost:without_random=True
boost:without_regex=True
boost:without_serialization=True
boost:without_stacktrace=True
boost:without_system=False
boost:without_test=True
boost:without_thread=True
boost:without_timer=True
boost:without_type_erasure=True
boost:without_wave=True
)
### Setup dependecies
if(CMAKE_CONFIGURATION_TYPES)
foreach(TYPE ${CMAKE_CONFIGURATION_TYPES})
message(STATUS "Configuring packages for ${TYPE}")
conan_cmake_configure(
${DEPENDENCIES}
GENERATORS
cmake_find_package_multi
)
conan_cmake_autodetect(settings BUILD_TYPE ${TYPE})
conan_cmake_install(PATH_OR_REFERENCE .
BUILD missing
SETTINGS ${settings}
)
endforeach()
else()
message(STATUS "Configuring packages for ${CMAKE_BUILD_TYPE}")
conan_cmake_configure(
${DEPENDENCIES}
GENERATORS
cmake_find_package_multi
)
conan_cmake_autodetect(settings)
conan_cmake_install(PATH_OR_REFERENCE .
BUILD missing
SETTINGS ${settings}
)
endif()
find_package(fmt CONFIG)
find_package(SQLite3 CONFIG)
find_package(SQLiteCpp CONFIG)
find_package(gflags CONFIG)
find_package(utf8cpp CONFIG)
find_package(Boost)
find_package(fmt REQUIRED)
find_package(SQLite3 REQUIRED)
find_package(SQLiteCpp REQUIRED)
find_package(gflags REQUIRED)
find_package(utf8cpp REQUIRED)
find_package(Boost REQUIRED COMPONENTS filesystem system)
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
@@ -150,9 +60,9 @@ add_executable(audacity-project-tools
target_link_libraries(audacity-project-tools
fmt::fmt
SQLite::SQLite3
SQLiteCpp::SQLiteCpp
SQLiteCpp
gflags::gflags
utf8cpp::utf8cpp
utf8cpp
Boost::boost
Boost::filesystem
Boost::system
@@ -166,7 +76,7 @@ set(CPACK_PACKAGE_NAME "audacity-project-tools")
set(CPACK_PACKAGE_VENDOR "Audacity")
set(CPACK_PACKAGE_VERSION_MAJOR 1)
set(CPACK_PACKAGE_VERSION_MINOR 0)
set(CPACK_PACKAGE_VERSION_PATCH 4)
set(CPACK_PACKAGE_VERSION_PATCH 5)
set(CPACK_GENERATOR ZIP)
SET(CPACK_OUTPUT_FILE_PREFIX packages)

44
conanfile.txt Normal file
View File

@@ -0,0 +1,44 @@
[requires]
fmt/7.1.3
sqlite3/3.45.3
sqlitecpp/3.3.3
gflags/2.2.2
utfcpp/3.2.1
boost/1.84.0
[generators]
CMakeDeps
CMakeToolchain
[options]
boost/*:without_atomic=False
boost/*:without_chrono=True
boost/*:without_container=True
boost/*:without_context=True
boost/*:without_contract=True
boost/*:without_coroutine=True
boost/*:without_date_time=True
boost/*:without_exception=True
boost/*:without_fiber=True
boost/*:without_filesystem=False
boost/*:without_graph=True
boost/*:without_graph_parallel=True
boost/*:without_iostreams=True
boost/*:without_json=True
boost/*:without_locale=True
boost/*:without_log=True
boost/*:without_math=True
boost/*:without_mpi=True
boost/*:without_nowide=True
boost/*:without_program_options=True
boost/*:without_python=True
boost/*:without_random=True
boost/*:without_regex=True
boost/*:without_serialization=True
boost/*:without_stacktrace=True
boost/*:without_system=False
boost/*:without_test=True
boost/*:without_thread=True
boost/*:without_timer=True
boost/*:without_type_erasure=True
boost/*:without_wave=True

View File

@@ -1 +1 @@
conan==1.59.0
conan>=2

View File

@@ -25,7 +25,7 @@ namespace
constexpr int64_t AudacityProjectID = 1096107097;
constexpr int64_t MaxSupportedVersion =
(3 << 24) + (4 << 16) + (0 << 8) + 0; // 3.4.0.0
(3 << 24) + (7 << 16) + (0 << 8) + 0; // 3.7.0.0
template<typename T> size_t readInt(const std::string& string, size_t offset, T& output)
{
@@ -132,6 +132,12 @@ AudacityDatabase::AudacityDatabase(
}, true);
}
AudacityDatabase::~AudacityDatabase()
{
mDatabase.reset();
removeJournalFiles(mReadOnly ? mProjectPath : mWritablePath);
}
void AudacityDatabase::reopenReadonlyAsWritable()
{
if (!mReadOnly)
@@ -466,22 +472,26 @@ void AudacityDatabase::extractTrack(
waveFile.writeFile();
}
void AudacityDatabase::removeJournalFiles(const std::filesystem::path& dbPath)
{
auto walFile = dbPath;
walFile.replace_extension("aup3-wal");
if (std::filesystem::exists(walFile))
std::filesystem::remove(walFile);
auto shmFile = dbPath;
shmFile.replace_extension("aup3-shm");
if (std::filesystem::exists(shmFile))
std::filesystem::remove(shmFile);
}
void AudacityDatabase::removeOldFiles()
{
if (std::filesystem::exists(mWritablePath))
{
std::filesystem::remove(mWritablePath);
auto walFile = mWritablePath;
walFile.replace_extension("aup3-wal");
if (std::filesystem::exists(walFile))
std::filesystem::remove(walFile);
auto shmFile = mWritablePath;
shmFile.replace_extension("aup3-shm");
if (std::filesystem::exists(shmFile))
std::filesystem::remove(shmFile);
removeJournalFiles(mWritablePath);
}
}

View File

@@ -24,6 +24,7 @@ class AudacityDatabase final
public:
explicit AudacityDatabase(
const std::filesystem::path& path, RecoveryConfig recoveryConfig);
~AudacityDatabase();
void reopenReadonlyAsWritable();
void recoverDatabase();
@@ -43,6 +44,7 @@ public:
void extractTrack(SampleFormat format, int32_t sampleRate, bool asStereo);
private:
static void removeJournalFiles(const std::filesystem::path& dbPath);
void removeOldFiles();
std::unique_ptr<SQLite::Database> mDatabase;

View File

@@ -281,17 +281,10 @@ class IdsLookup final
public:
void store(uint16_t index, std::string value)
{
const auto size = mIds.size();
if (index >= mIds.size())
mIds.resize(index + 1);
if (index == size)
mIds.push_back(std::move(value));
else
{
if ((index + 1) < size)
mIds.resize(index);
mIds[index] = std::move(value);
}
mIds[index] = std::move(value);
}
std::string_view get(uint16_t index)

View File

@@ -86,6 +86,7 @@ Buffer::read(void* data, size_t offset, size_t size) const noexcept
offset = 0;
bytesLeft -= chunkSize;
outPtr += chunkSize;
++chunk;
}
return size;

View File

@@ -48,12 +48,12 @@ public:
return 0;
const size_t chunkIndex = offset / BUFFER_SIZE;
offset = offset - BUFFER_SIZE * chunkIndex;
const size_t chunkOffset = offset - BUFFER_SIZE * chunkIndex;
if (BUFFER_SIZE < (offset + size))
if (BUFFER_SIZE < (chunkOffset + size))
return read(&data, offset, size);
const void* ptr = mChunks[chunkIndex].data() + offset;
const void* ptr = mChunks[chunkIndex].data() + chunkOffset;
data = *static_cast<const T*>(ptr);

View File

@@ -483,15 +483,22 @@ void AudacityProject::removeUnusedBlocks()
readBlocksList.reset();
std::set<int64_t> orphanedBlocks;
std::set<int64_t> usedBlocks;
for (const auto block : mWaveBlocks)
for (const auto& block : mWaveBlocks)
{
if (block.isSilence())
continue;
if (availableBlocks.count(block.getBlockId()) == 0)
orphanedBlocks.emplace(block.getBlockId());
usedBlocks.emplace(block.getBlockId());
}
std::set<int64_t> orphanedBlocks;
for (auto blockId : availableBlocks)
{
if (usedBlocks.count(blockId) == 0)
orphanedBlocks.emplace(blockId);
}
mDb.reopenReadonlyAsWritable();

View File

@@ -55,7 +55,15 @@ void GetAttributeValue(const AttributeValue& attr, Ret& result)
{
if constexpr (std::is_same_v<Ret, bool>)
{
result = arg == "true" || arg == "0";
result = arg == "true" || arg == "1";
}
else if constexpr (std::is_floating_point_v<Ret>)
{
std::string tmp(arg);
if constexpr (std::is_same_v<Ret, float>)
result = std::stof(tmp);
else
result = static_cast<Ret>(std::stod(tmp));
}
else
{