pax_global_header00006660000000000000000000000064146307532420014520gustar00rootroot0000000000000052 comment=96f153da0df93f7ee8733377001d8a27b4f83f72 rsgain-3.5.1/000077500000000000000000000000001463075324200130115ustar00rootroot00000000000000rsgain-3.5.1/.gitattributes000066400000000000000000000000421463075324200157000ustar00rootroot00000000000000src/external/** linguist-vendored rsgain-3.5.1/.github/000077500000000000000000000000001463075324200143515ustar00rootroot00000000000000rsgain-3.5.1/.github/workflows/000077500000000000000000000000001463075324200164065ustar00rootroot00000000000000rsgain-3.5.1/.github/workflows/build.yml000066400000000000000000000171461463075324200202410ustar00rootroot00000000000000name: Build on: push: paths-ignore: - "**/**.md" pull_request: branches: - master paths-ignore: - "**/**.md" workflow_dispatch: permissions: actions: none checks: none contents: write deployments: none issues: none packages: read pull-requests: none repository-projects: none security-events: none statuses: read defaults: run: shell: bash env: VCPKG_COMMITTISH: 01f602195983451bc83e72f4214af2cbc495aa94 jobs: build_windows: name: Windows runs-on: windows-2022 strategy: fail-fast: false env: CMAKE_BUILD_TYPE: Release CMAKE_GENERATOR: Visual Studio 17 2022 VCPKG_TRIPLET: custom-triplet steps: - name: Checkout Git repository uses: actions/checkout@v3 with: submodules: true fetch-depth: 0 - name: Setup vcpkg uses: friendlyanon/setup-vcpkg@v1 with: committish: ${{env.VCPKG_COMMITTISH}} - name: Setup Overlays uses: actions/checkout@v3 with: repository: complexlogic/vcpkg ref: refs/heads/rsgain path: build/overlays - name: Configure run: cmake -S . -B build -G "${{env.CMAKE_GENERATOR}}" -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_MANIFEST_FEATURES="ffmpeg;libebur128;inih;" -DVCPKG_OVERLAY_PORTS=build/overlays/ports -DVCPKG_OVERLAY_TRIPLETS=config/vcpkg_triplets -DVCPKG_TARGET_TRIPLET=${{env.VCPKG_TRIPLET}} - name: Build run: | cmake \ --build build \ --target package \ --config ${{ env.CMAKE_BUILD_TYPE }} build/${{env.CMAKE_BUILD_TYPE}}/rsgain.exe -v - name: Upload Package uses: actions/upload-artifact@v3 with: name: Windows build path: build/*.zip - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: build/*.zip build_linux: name: Linux runs-on: ubuntu-latest permissions: packages: write strategy: fail-fast: false matrix: config: - name: Debian docker_image: debian:bookworm package_type: DEB package_ext: .deb vcpkg_features: fmt; - name: Fedora docker_image: fedora:39 package_type: RPM package_ext: .rpm vcpkg_features: fmt; - name: Static docker_image: debian:bullseye package_type: TXZ package_ext: .tar.xz vcpkg_features: fmt;ffmpeg;libebur128;inih; container: image: ${{matrix.config.docker_image}} env: CMAKE_BUILD_TYPE: Release VCPKG_TRIPLET: x64-linux steps: - name: Checkout Git repository uses: actions/checkout@v3 with: fetch-depth: 0 - name: "Install dependencies" run: | if [[ "${{matrix.config.name}}" == "Debian" ]]; then apt update && apt install -y curl zip unzip gzip tar build-essential git cmake pkg-config libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libebur128-dev libinih-dev binutils fi if [[ "${{matrix.config.name}}" == "Fedora" ]]; then dnf install -y curl zip unzip gzip tar git make pkg-config gcc-c++ fedora-packager rpmdevtools cmake libavcodec-free-devel libavformat-free-devel libswresample-free-devel libavutil-free-devel libebur128-devel inih-devel fi if [[ "${{matrix.config.name}}" == "Static" ]]; then apt update && apt install -y curl zip unzip tar build-essential git cmake pkg-config python3 nasm binutils fi - name: Setup vcpkg uses: friendlyanon/setup-vcpkg@v1 with: committish: ${{env.VCPKG_COMMITTISH}} cache-key: vcpkg-${{matrix.config.name}}-${{env.VCPKG_COMMITISH}} cache-restore-keys: vcpkg-${{matrix.config.name}}-${{env.VCPKG_COMMITISH}} - name: Setup Overlays uses: actions/checkout@v3 with: repository: complexlogic/vcpkg ref: refs/heads/rsgain path: build/overlays - name: Configure run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=${{env.VCPKG_TRIPLET}} -DVCPKG_MANIFEST_FEATURES="${{matrix.config.vcpkg_features}}" -DVCPKG_OVERLAY_PORTS=build/overlays/ports -DCMAKE_BUILD_TYPE=${{env.CMAKE_BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=/usr -DPACKAGE=${{matrix.config.package_type}} -DSTRIP_BINARY=ON -DINSTALL_MANPAGE=${{matrix.config.name == 'Debian' && 'ON' || 'OFF'}} - name: Build run: | cmake \ --build build \ --target package build/rsgain -v - name: Upload Package uses: actions/upload-artifact@v3 with: name: ${{matrix.config.name}} build path: build/*${{matrix.config.package_ext}} - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: build/*${{matrix.config.package_ext}} token: ${{secrets.ACTIONS_SECRET}} build_macos: name: macOS runs-on: macos-12 permissions: packages: write strategy: fail-fast: false matrix: config: - name: Intel OSX_ARCH: x86_64 VCPKG_TRIPLET: x64-osx - name: Apple Silicon OSX_ARCH: arm64 VCPKG_TRIPLET: arm64-osx env: CMAKE_BUILD_TYPE: Release steps: - name: Checkout Git repository uses: actions/checkout@v3 with: fetch-depth: 0 - name: Install Dependencies run: brew install nasm automake autoconf-archive ninja - name: Setup vcpkg uses: friendlyanon/setup-vcpkg@v1 with: committish: ${{env.VCPKG_COMMITTISH}} cache-key: vcpkg-${{matrix.config.name}}-${{env.VCPKG_COMMITISH}} cache-restore-keys: vcpkg-${{matrix.config.name}}-${{env.VCPKG_COMMITISH}} - name: Setup Overlays uses: actions/checkout@v3 with: repository: complexlogic/vcpkg ref: refs/heads/rsgain path: build/overlays - name: Configure run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=${{matrix.config.OSX_ARCH}} -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_OVERLAY_PORTS=build/overlays/ports -DVCPKG_TARGET_TRIPLET=${{matrix.config.VCPKG_TRIPLET}} -DVCPKG_MANIFEST_FEATURES="fmt;ffmpeg;libebur128;inih;" -DSTRIP_BINARY=ON -DPACKAGE=ZIP -DCPACK_SYSTEM_NAME="macOS-${{matrix.config.OSX_ARCH}}" - name: Build run: | cmake \ --build build \ --target package - name: Test if: ${{matrix.config.OSX_ARCH == 'x86_64'}} run: build/rsgain -v - name: Upload Package uses: actions/upload-artifact@v3 with: name: macOS ${{matrix.config.name}} build path: build/*.zip - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: build/*.zip token: ${{secrets.ACTIONS_SECRET}}rsgain-3.5.1/.github/workflows/release.yml000066400000000000000000000027161463075324200205570ustar00rootroot00000000000000name: Publish Source on: release: types: [published] workflow_dispatch: defaults: run: shell: bash permissions: actions: none checks: none contents: write deployments: none issues: none packages: read pull-requests: none repository-projects: none security-events: none statuses: read jobs: build_source: name: Build Source if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest strategy: fail-fast: false steps: - name: Setup run: | sudo apt install -y tar REPO=${{github.repository}} REPO_TITLE=${REPO#*/} RELEASE_TITLE=${{github.event.release.name}} PACKAGE_TITLE=${REPO_TITLE}-${RELEASE_TITLE#*v} echo "PACKAGE_TITLE=${PACKAGE_TITLE}" >> ${GITHUB_ENV} - name: Checkout Git repository uses: actions/checkout@v3 with: path: ${{env.PACKAGE_TITLE}} - name: Package run: | ARCHIVE_NAME=${{env.PACKAGE_TITLE}}-source.tar.xz tar --lzma --exclude ${{env.PACKAGE_TITLE}}/.git -cf $ARCHIVE_NAME ${{env.PACKAGE_TITLE}} SHA256=$(shasum -b -a 256 < $ARCHIVE_NAME | cut -d ' ' -f1) echo "SHA256=${SHA256}" >> ${GITHUB_ENV} - name: Release uses: softprops/action-gh-release@master with: files: ./*.tar.xz append_body: true body: "**Source SHA256:** ${{env.SHA256}}" generate_release_notes: true rsgain-3.5.1/.gitignore000066400000000000000000000000071463075324200147760ustar00rootroot00000000000000/build rsgain-3.5.1/CHANGELOG000066400000000000000000000155701463075324200142330ustar00rootroot00000000000000v3.5.1 (2024-06-08) - Fix header gain calculation for multichannel Opus files - Static builds: Upgrade to FFmpeg 7 v3.5 (2024-02-25) - Add -p option to preserve file modification times - In Custom Mode, immediately fail if any file in the list does not exist or is of unsupported type - Fix segfault that occurs when attempting to tag corrupted MP4 files - Provide static builds for macOS and Linux - Unix: Include optional manpage with installation - Windows: Support long paths - Static builds: Upgrade to TagLib 2 v3.4 (2023-09-11) - Added support for Tom's lossless Audio Kompressor (TAK) format - Added -O option 'a' which sorts the output by filename alphanumerically - Added Custom Mode -o option 's' which forces Opus files to normalize to -23 LUFS regardless of the target loudness setting - Support files with .mp4 file extension - Introduce Easy Mode preset option "SkipMP4" which allows users to opt out of the new behavior - When scanning completely silent tracks, the program now writes "0.00 dB" and "0.000000" as gain and peak tags, respectively. Previously, the program would completely skip over these files without writing any tags to them - Silent tracks are not included in album gain calculations to keep results consistent with previous versions - -I 'keep' mode now default in both Custom Mode and Easy Mode - Fix TagMode 'n' option (regression from v3.2) v3.3 (2023-04-23) - New -I mode 'keep' detects the file's existing ID3v2 version, and preserves it - Less disruptive to files, less prone to data loss as TagLib sometimes discards frames when converting between ID3v2 versions. - Will be made the default mode in a future release - Handle file extensions case insensitively when determining file type - When removing ReplayGain tags from ID3 formats, also remove legacy RVAD and RVA2 ("Relative Volume Adjustment") frames which conflict with ReplayGain - Fix segfault caused by unusual file durations - Added -O 's' mode to write sep header to CSV output for Microsoft Excel compatibility. - Mode argument for -O is optional to maintain backwards compatibility - Windows: Search for Easy Mode presets in a default user directory similar to the macOS and Linux versions - Windows: Upgrade to FFmpeg 6 v3.2.1 (2023-02-26) - Fix minor bug with Easy Mode statistics output - Port to new FFmpeg channel layout API - Remove deprecated TagLib functions v3.2 (2023-02-10) - Support scanning directories with multiple file types in Easy Mode - Redesigned the multithreading mode to increase reliability and effiency - Progress update in multithreading mode now detects the width of the console and limits it so the output doesn't wrap to a new line on long directory names - Fix long options - Fix a segfault that occurs when scanning some corrupt MP3 files - Windows: Upgrade to FFmpeg 5 v3.1.1 (2022-11-15) - Fix ID3v2 version parsing bug v3.1 (2022-11-04) - Added support for Musepack format - Added support for HE-AAC, xHE-AAC formats - Added option '-S' to skip files with existing ReplayGain data - Easy mode: Added tag mode 'n' which will always skip files regardless of whether or not they have existing ReplayGain tags - Always fail if a requested command line option or scan preset is invalid - Build binary packages via GitHub Actions CI - Added support for BSD operating systems v3.0.1 (2022-10-13) - Fixed inconsistencies in tab output formatting v3.0 (2022-09-14) - Massive internal rewrite: - Source code is now purely C++ instead of mixed C/C++ - Text formating and output now utilize the fmt library, which is safer and typically faster than C stdio implementations - Easy Mode multithreaded now generates scan jobs per-directory instead of per-file, which provides an approximately 15-20% performance boost due to reduced thread idling time - Added progress updates for Easy Mode multithreaded - Added -m MAX option to use the maximum concurrent threads value provided by the OS - Fixed a critical bug inherited from loudgain where the program segfaults when trying to delete tags from M4A or WMA files - Added -t option which allows users to choose between sample peak and true peak for peak calculations. Sample peak now default in both Easy Mode and Custom Mode - Added -O option in Easy Mode which outputs a .csv spreadsheet scan log in every scanned directory - Added more statistics to Easy Mode output - Write uppercase tags by default for all tag types in Easy Mode - Optimized scan progress bar, resulting in significant speed boost for Windows single threaded scanning - "Overrides" feature renamed "Preset" - Specified with -p instead of -o - Support preset name as argument, which will automatically find the preset in the predetermined location(s) per platform - Support "Global" section in preset, which allows the user to specify settings for all formats to reduce duplication - Changed the name of setting keys in accordance with command line flags - Ship EBU R 128 preset and "loudgain" preset based on rgbpm settings - Removed tag modes that enabled non-standard behavior: - -s e mode which wrote non-standard REPLAYGAIN_REFERENCE_LOUDNESS and REPLAYGAIN_*_RANGE tags - -s l mode which wrote non-standard LU units to *_GAIN tags instead of dB - Changed how target loudness is specified: - Removed -d option which specified a "pre-gain" from -18 LUFS - Replaced with -l option which allows the user to specify the target loudness directly in LUFS - Write ID3v2.3 tags by default in Custom Mode - Changed clipping protection: - Now specified with -c and 3 choice character option instead of a boolean - Old -k functionality is now -c a - New -c p functionality only provides clipping protection for positive gain values, which is now the default in Easy Mode - Removed strip tags option (-S) - Changed max peak level command line char from -K to -m - Changed behavior for Opus files: - Introduced -o option with required char argument - Support writing to header output gain - default 'd' mode: Write standard ReplayGain tags, set output gain to 0 - 'r' mode: Write RFC 7845 R128_*_GAIN tags, set output gain to 0 - 't' and 'a' modes write the track gain and album gain to the header output gain, respectively - Keep default loudness at -18 LUFS (can be changed with -l option) - Windows: Added resource file containing VERSIONINFO - Windows: Support static linking of dependencies - Linux: Enforce a minimum taglib version 1.12. Provide a static taglib build for the .deb package v2.0.1 (2022-05-18) - Set max thread sleep period to prevent hanging in multithreaded mode v2.0 (2022-05-10) - Changed project name to rsgain - Added "Easy Mode" with recursive directory scanning. Old command line syntax now known as "Custom Mode" - Added multithreaded scanning - Added override feature for Easy Mode v1.0.1 (2022-04-15) - Refactored scan.py script, renamed to loudgain-scanner.py - Strictly enforce libebur128 minimum version 1.2.4 v1.0 (2022-02-21) - Fork from Moonbase59/loudgain v0.6.8 - Native Windows port rsgain-3.5.1/CMakeLists.txt000066400000000000000000000221661463075324200155600ustar00rootroot00000000000000# rsgain CMakeLists.txt # Copyright (C) 2014 Alessandro Ghedini # Modifications Copyright (C) 2019 Matthias C. Hormann # rsgain by complexlogic, 2022 # This file is released under the 2 clause BSD license, see COPYING cmake_minimum_required(VERSION 3.13) option(VCPKG "Build dependencies with vcpkg" OFF) if (VCPKG) include("${CMAKE_SOURCE_DIR}/config/vcpkg.cmake") endif () project(rsgain VERSION 3.5.1 DESCRIPTION "ReplayGain 2.0 loudness normalizer" HOMEPAGE_URL "https://github.com/complexlogic/rsgain" LANGUAGES CXX ) set(MAXPROGBARWIDTH "0" CACHE STRING "Maximum width of progress bar") option(UCHECKMARKS "Enable use of Unicode checkmarks" ON) option(EXTRA_WARNINGS "Enable extra compiler warnings" OFF) option(INSTALL_MANPAGE "Install man page (requires gzip)" OFF) if (EXTRA_WARNINGS) if (MSVC) add_compile_options(/W4 /WX) else () add_compile_options(-Wall -Wextra -Wpedantic -Wconversion) endif () endif () set(CMAKE_CXX_STANDARD 20) set(EXECUTABLE_TITLE "rsgain") include_directories(${PROJECT_BINARY_DIR}) add_compile_definitions("$<$:DEBUG>") if (WIN32) set (USE_STD_FORMAT true) if (MSVC_VERSION AND MSVC_VERSION VERSION_LESS 1937) message(FATAL_ERROR "Visual Studio 17.7 and later supported only") endif () endif () if (USE_STD_FORMAT) if (UNIX) include(CheckIncludeFiles) CHECK_INCLUDE_FILES("format;print" SUPPORT_STD_FORMAT LANGUAGE CXX) if (NOT SUPPORT_STD_FORMAT) message(FATAL_ERROR "You do not have the required system headers for std::format and/or std::print") endif () endif () add_compile_definitions(USE_STD_FORMAT) set(CMAKE_CXX_STANDARD 23) endif () # GCC 9 and earlier are not supported due to C++20 features if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10) message(FATAL_ERROR "GCC 10 and later supported only") endif () # Set Visual Studio startup project if (WIN32) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${EXECUTABLE_TITLE}) endif () # Embed Git information find_package(Git QUIET) if (Git_FOUND) execute_process(COMMAND "${GIT_EXECUTABLE}" describe --long --tags WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_OUTPUT RESULT_VARIABLE GIT_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE ) if (GIT_RESULT EQUAL 0) string(REPLACE "-" ";" GIT_LIST "${GIT_OUTPUT}") list(GET GIT_LIST 1 COMMITS_SINCE_TAG) if (NOT COMMITS_SINCE_TAG STREQUAL "0") list(GET GIT_LIST 2 COMMIT_HASH) string(REPLACE "g" "" COMMIT_HASH "${COMMIT_HASH}") add_compile_definitions(COMMITS_SINCE_TAG=\"${COMMITS_SINCE_TAG}\") add_compile_definitions(COMMIT_HASH=\"${COMMIT_HASH}\") endif () endif () endif () # Find dependencies - Windows if (WIN32) find_path(FFMPEG_INCLUDE_DIR "libavformat/avformat.h" REQUIRED) find_library(LIBAVFORMAT avformat REQUIRED) find_library(LIBAVCODEC avcodec REQUIRED) find_library(LIBAVUTIL avutil REQUIRED) find_library(LIBSWRESAMPLE swresample REQUIRED) find_path(TAGLIB_INCLUDE_DIR "taglib/id3v2tag.h" REQUIRED) find_library(TAGLIB tag REQUIRED) find_path(LIBEBUR128_INCLUDE_DIR "ebur128.h" REQUIRED) find_library(LIBEBUR128 ebur128 REQUIRED) find_path(GETOPT_INCLUDE_DIR "getopt.h" REQUIRED) find_library(GETOPT getopt REQUIRED) find_path(INIH_INCLUDE_DIR "ini.h" REQUIRED) find_library(INIH inih REQUIRED) find_package(fdk-aac CONFIG REQUIRED) if (VCPKG_TARGET_TRIPLET STREQUAL "custom-triplet") find_library(ZLIB zlib REQUIRED) set(STATIC_LIBS ${ZLIB} ws2_32.lib secur32.lib mfplat.lib mfuuid.lib strmiids.lib bcrypt.lib) endif () # Find dependencies - Linux/Mac elseif (UNIX) find_package(PkgConfig MODULE REQUIRED) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) pkg_check_modules(LIBAVCODEC REQUIRED IMPORTED_TARGET libavcodec) pkg_check_modules(LIBAVFORMAT REQUIRED IMPORTED_TARGET libavformat) pkg_check_modules(LIBSWRESAMPLE REQUIRED IMPORTED_TARGET libswresample) pkg_check_modules(LIBAVUTIL REQUIRED IMPORTED_TARGET libavutil) pkg_check_modules(TAGLIB REQUIRED IMPORTED_TARGET taglib>=1.11.1) pkg_check_modules(LIBEBUR128 REQUIRED IMPORTED_TARGET libebur128>=1.2.4) pkg_check_modules(INIH REQUIRED IMPORTED_TARGET inih) if (STRIP_BINARY) find_program(STRIP strip REQUIRED) endif () if (INSTALL_MANPAGE) find_program(GZIP gzip REQUIRED) endif () if (NOT USE_STD_FORMAT) pkg_check_modules(FMT REQUIRED IMPORTED_TARGET fmt) endif () endif() # Generate Windows application manifest and resource file if (WIN32) set(VERSION_M ${PROJECT_VERSION_MAJOR}) set(VERSION_N ${PROJECT_VERSION_MINOR}) if (PROJECT_VERSION_PATCH) set(VERSION_O ${PROJECT_VERSION_PATCH}) else () set(VERSION_O 0) endif() if (PROJECT_VERSION_TWEAK) set(VERSION_P ${PROJECT_VERSION_TWEAK}) else () set(VERSION_P 0) endif() configure_file(${PROJECT_SOURCE_DIR}/config/${PROJECT_NAME}.manifest.in ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.manifest) configure_file(${PROJECT_SOURCE_DIR}/config/versioninfo.rc.in ${PROJECT_BINARY_DIR}/versioninfo.rc) endif() # Build source files add_subdirectory(src) # Installation - Windows if (WIN32) install(DIRECTORY ${PROJECT_BINARY_DIR}/$/ DESTINATION .) foreach(item CHANGELOG LICENSE LICENSE-CRCpp) configure_file("${PROJECT_SOURCE_DIR}/${item}" "${PROJECT_BINARY_DIR}/${item}.txt") install(FILES "${PROJECT_BINARY_DIR}/${item}.txt" DESTINATION .) endforeach () # Install presets file(GLOB presets ${PROJECT_SOURCE_DIR}/config/presets/*.ini) foreach(preset ${presets}) get_filename_component(preset_name ${preset} NAME) configure_file(${preset} ${PROJECT_BINARY_DIR}/presets/${preset_name}) endforeach () install(DIRECTORY "${PROJECT_BINARY_DIR}/presets" DESTINATION .) # Copy the Visual C++ runtime DLLs in case user doesn't have them installed set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE) include(InstallRequiredSystemLibraries) foreach(required_lib vcruntime140.dll vcruntime140_1.dll msvcp140.dll) foreach(system_lib ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}) string(FIND ${system_lib} ${required_lib} found_lib) if (NOT found_lib EQUAL -1) install(FILES ${system_lib} DESTINATION .) endif () endforeach () endforeach () # Set up CPack set(CPACK_PACKAGE_NAME ${EXECUTABLE_TITLE}) set(CPACK_GENERATOR "ZIP") include(CPack) # Installation - Linux/Mac elseif (UNIX) install(TARGETS ${EXECUTABLE_TITLE} DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") if (INSTALL_MANPAGE) install(FILES "${PROJECT_BINARY_DIR}/${EXECUTABLE_TITLE}.1.gz" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/man/man1") endif () install(DIRECTORY "${PROJECT_SOURCE_DIR}/config/presets" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/${EXECUTABLE_TITLE}") if (PACKAGE STREQUAL "TXZ" OR PACKAGE STREQUAL "ZIP") set(BINARY_PREFIX ".") set(PRESETS_PREFIX ".") else () set(BINARY_PREFIX "${CMAKE_INSTALL_PREFIX}/bin") set(PRESETS_PREFIX "${CMAKE_INSTALL_PREFIX}/share/${EXECUTABLE_TITLE}") endif () install(TARGETS ${EXECUTABLE_TITLE} DESTINATION "${BINARY_PREFIX}") install(DIRECTORY "${PROJECT_SOURCE_DIR}/config/presets" DESTINATION "${PRESETS_PREFIX}") set(PRESETS_DIR ${CMAKE_INSTALL_PREFIX}/share/${EXECUTABLE_TITLE}/presets) # Build Debian packages if (PACKAGE STREQUAL "DEB") set(CPACK_DEBIAN_FILE_NAME "DEB-DEFAULT") if (COMMITS_SINCE_TAG AND COMMIT_HASH) set(CPACK_DEBIAN_PACKAGE_VERSION "${PROJECT_VERSION}-r${COMMITS_SINCE_TAG}-${COMMIT_HASH}") endif () set(CPACK_DEBIAN_PACKAGE_DEPENDS "libavcodec59 (>= 5.1), libavutil57 (>= 5.1), libswresample4 (>= 5.1), libavformat59 (>= 5.1), libebur128-1 (>=1.2.4), libinih1, libc6 (>=2.29), libstdc++6 (>=10.2), zlib1g") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "complexlogic") set(CPACK_DEBIAN_PACKAGE_SECTION "utils") set(CPACK_DEBIAN_ARCHIVE_TYPE "gnutar") set(CPACK_DEBIAN_COMPRESSION_TYPE "gzip") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") # Build Fedora packages elseif (PACKAGE STREQUAL "RPM") set(CPACK_RPM_FILE_NAME "RPM-DEFAULT") if (COMMITS_SINCE_TAG AND COMMIT_HASH) set(CPACK_RPM_PACKAGE_VERSION "${PROJECT_VERSION}-r${COMMITS_SINCE_TAG}-${COMMIT_HASH}") endif () set(CPACK_RPM_PACKAGE_LICENSE "BSD") set(CPACK_RPM_PACKAGE_GROUP "Applications/Multimedia") set(CPACK_RPM_PACKAGE_AUTOREQPROV 0) set(CPACK_RPM_PACKAGE_REQUIRES "libavcodec-free >= 6, libavformat-free >= 6, libswresample-free >= 6, libavutil-free >= 6, libebur128, zlib, inih") elseif (PACKAGE STREQUAL "TXZ") set(CPACK_ARCHIVE_FILE_EXTENSION ".tar.xz") endif () if (PACKAGE) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_DESCRIPTION}) set(CPACK_PACKAGE_NAME ${EXECUTABLE_TITLE}) set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) set(CPACK_GENERATOR ${PACKAGE}) include(CPack) endif () configure_file( "${PROJECT_SOURCE_DIR}/config/cmake_uninstall.cmake.in" "${PROJECT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${PROJECT_BINARY_DIR}/cmake_uninstall.cmake) endif() configure_file("${PROJECT_SOURCE_DIR}/config/config.h.in" "${PROJECT_BINARY_DIR}/config.h") rsgain-3.5.1/Dockerfile000066400000000000000000000006461463075324200150110ustar00rootroot00000000000000FROM debian:bookworm ARG VERSION=3.5.1 \ ARCH=amd64 RUN apt-get update && \ apt-get install -y --no-install-recommends curl ca-certificates openssl && \ curl -sSL -o /tmp/rsgain.deb "https://github.com/complexlogic/rsgain/releases/download/v${VERSION}/rsgain_${VERSION}_${ARCH}.deb" && \ apt install -y /tmp/rsgain.deb && \ rm -rf /var/lib/apt/lists/* /tmp/rsgain.deb ENTRYPOINT ["/usr/bin/rsgain"] rsgain-3.5.1/LICENSE000066400000000000000000000026231463075324200140210ustar00rootroot00000000000000Copyright (c) 2014, Alessandro Ghedini v0.1-v0.6.8: Copyright (C) 2019 Matthias C. Hormann rsgain by complexlogic, 2022 All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. rsgain-3.5.1/LICENSE-CRCpp000066400000000000000000000027101463075324200147630ustar00rootroot00000000000000CRC++ Copyright (c) 2022, Daniel Bahr All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of CRC++ nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. rsgain-3.5.1/README.md000066400000000000000000001026501463075324200142740ustar00rootroot00000000000000# rsgain ## Table of Contents 1. [About](#about) 2. [Installation](#installation) - [Windows](#windows) - [macOS](#macos) - [Linux](#linux) - [FreeBSD](#freebsd) - [Android](#android) - [Docker](#docker) 3. [Supported File Formats](#supported-file-formats) 4. [Usage](#usage) - [Easy Mode](#easy-mode) - [Custom Mode](#custom-mode) - [MusicBrainz Picard Plugin](#musicbrainz-picard-plugin) 5. [Design Philosophy](#design-philosophy) 6. [License](#license) ## About **rsgain** (**r**eally **s**imple **gain**) is a ReplayGain 2.0 command line utility for Windows, macOS, Linux, BSD, and Android. rsgain applies loudness metadata tags to your files, while leaving the audio stream untouched. A ReplayGain-compatible player will dynamically adjust the volume of your tagged files during playback. rsgain is designed with a "batteries included" philosophy, allowing a user to scan their entire music library without requiring external scripts or other tools. It aims to strike the perfect balance between power and simplicity by providing multiple user interfaces. See [Usage](#usage) for more information. rsgain is the backend for the [MusicBrainz Picard](https://picard.musicbrainz.org/) ReplayGain 2.0 plugin. Users that are not comfortable with command line interfaces may prefer this method since the plugin provides a GUI frontend to rsgain. See [MusicBrainz Picard Plugin](#musicbrainz-picard-plugin) for more information. ## Installation Binary packages are available on the [Release Page](https://github.com/complexlogic/rsgain/releases) for Windows, macOS, and some Linux distributions. You can also build the program yourself, see [BUILDING](docs/BUILDING.md). ### Windows Download the ZIP file from the link below and extract its contents to a folder of your choice: - [rsgain v3.5.1 portable ZIP (x64)](https://github.com/complexlogic/rsgain/releases/download/v3.5.1/rsgain-3.5.1-win64.zip) rsgain should be run on Windows 10 or later for full compatibility, but it can run on Windows versions as early as Vista with some caveats. See [Windows Notes](#windows-notes) for more information. It is recommended to add the directory to your `Path` system environment variable so you can invoke the program with the `rsgain` command instead of the path to its .exe file. 1. Use Windows key + R to bring up the run box, then type `sysdm.cpl` and press enter 2. In the resulting window in the "Advanced" tab, click the "Environment variables" button. 3. In the next window under "System variables", select "Path", then press "Edit". 4. Add the folder that you extracted `rsgain.exe` to. #### Scoop rsgain is available in the [Scoop](https://scoop.sh/) extras bucket. Installing via Scoop enables you to receive automatic upgrades to future versions, unlike the manual installation method described above. First, make sure you have enabled the extras bucket. Then, install using the command below: ```powershell scoop install extras/rsgain ``` ### macOS Separate builds are available for Apple Silicon and Intel based Macs. Both require macOS 12 (Monterey) or later. Download and extract the correct version according to your hardware: - [rsgain v3.5.1 portable ZIP (Apple Silicon)](https://github.com/complexlogic/rsgain/releases/download/v3.5.1/rsgain-3.5.1-macOS-arm64.zip) - [rsgain v3.5.1 portable ZIP (Intel)](https://github.com/complexlogic/rsgain/releases/download/v3.5.1/rsgain-3.5.1-macOS-x86_64.zip) These builds are not codesigned, and the macOS Gatekeeper will most likely block execution. To work around this, you can remove the quarantine bit using the command below: ```bash xattr -d com.apple.quarantine /path/to/rsgain ``` Substitute `/path/to/rsgain` with the actual path on your system. ### Linux #### Debian/Ubuntu rsgain is available as an official Debian package starting in Debian 13 (Trixie) and Ubuntu 24.04 (noble). Install via `apt`: ```bash sudo apt install rsgain ``` There is also a .deb package for Debian Bookworm available on the [release page](https://github.com/complexlogic/rsgain/releases/latest). Use the following commands to install: ```bash wget https://github.com/complexlogic/rsgain/releases/download/v3.5.1/rsgain_3.5.1-1_amd64.deb sudo apt install ./rsgain_3.5.1-1_amd64.deb ``` The above package won't work on recent Ubuntu releases due to an FFmpeg ABI break. #### Arch/Manjaro rsgain is available in the AUR via the packages [rsgain](https://aur.archlinux.org/packages/rsgain) and [rgsain-git](https://aur.archlinux.org/packages/rsgain-git). You can install with an AUR helper such as `yay`: ```bash yay -S rsgain ``` #### Fedora rsgain is packaged in Fedora's repositories. You can use `dnf` to install: ```bash sudo dnf install rsgain ``` #### Nix/NixOS `rsgain` is in [nixpkgs](https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/rs/rsgain/package.nix). Some options: - Run it without installing: ```sh nix run nixpkgs#rsgain ``` - Install it in your Nix environment: ```sh nix-env -f '' -iA rsgain ``` - Add it to your NixOS configuration: ```nix environment.systemPackages = with pkgs; [ rsgain ]; ``` #### Static Build An x86_64 static build is available that should run on recent releases of most GNU-based Linux distros (any distro shipping GCC 10 or later). Download the archive below and extract it to a directory of your choice: - [rsgain v3.5.1 portable TAR (x86_64)](https://github.com/complexlogic/rsgain/releases/download/v3.5.1/rsgain-3.5.1-Linux.tar.xz) ### FreeBSD Available via ports tree or using packages (2023Q1 and later) as listed below: ```bash cd /usr/ports/audio/rsgain && make install clean pkg install rsgain ``` ### Android rsgain can be installed on Android devices via the [Termux](https://termux.dev/en/) terminal emulator package manager: ```bash pkg install rsgain ``` ### Docker The repo contains a Dockerfile which can be used to run rsgain in a virtual environment. It will build a container based on the current Debian Stable release: ```bash docker build -t rsgain https://github.com/complexlogic/rsgain.git ``` rsgain requires a relatively up-to-date operating system, so this method can be used to run rsgain on an older system if necessary. A Docker container doesn't have access to the host filesystem by default. To use rsgain in a container, you need to mount your music library to a mount point in the container. Use the -v option followed by the path to your library and the mount point, separated by a colon. For example, if your music library is located at `/path/to/library`: ```bash docker run -v /path/to/library:/mnt rsgain easy -m MAX /mnt ``` The docker log to `stdout` updates too slowly for the scan progress bar. If you don't use multithreaded mode consider passing `-q` to silence the output. ## Supported file formats rsgain supports all popular file formats. See the below table for compatibility. rsgain sorts files internally based on file extension, so it is required that your audio files match one of the extensions in the second column of the table in order to be recognized as valid. | Format | Supported File Extension(s) | | ------------------------------------ | --------------------------- | | Audio Interchange File Format (AIFF) | .aiff | | Free Lossless Audio Codec (FLAC) | .flac | | Monkey's Audio | .ape | | MPEG-1 Audio Layer II (MP2) | .mp2 | | MPEG-1 Audio Layer III (MP3) | .mp3 | | MPEG-4 Audio (AAC, ALAC)¹ | .m4a | | Musepack (MPC)² | .mpc | | Ogg (Vorbis, Speex, FLAC) | .ogg, .oga, .spx | | Opus | .opus | | Tom's lossless Audio Kompressor | .tak | | Waveform Audio File Format (WAV) | .wav | | Wavpack | .wv | | Windows Media Audio (WMA) | .wma | 1. *Support for HE-AAC and xHE-AAC are available via the Fraunhofer FDK AAC library. For the static builds, the included FFmpeg was compiled with support, so no further action is required. For the dynamic builds, you will need to check if your build of FFmpeg was compiled with the '--enable-libfdk-aac' option, and compile it yourself if necessary* 2. *Stream Version 8 (SV8) supported only. If you have files in the older SV7 format, you can convert them losslessly to SV8* ## Usage rsgain contains two separate user interfaces: Easy Mode and Custom Mode. The distinction between the two modes is rooted in the history of ReplayGain utilities. Legacy ReplayGain tagging utilities such as mp3gain did not support recursive directory-based scanning. The user was required to manually specify a list of files on the command line, preceded by options which were numerous and complex. This interface provided a lot of power and flexibility, but it wasn't particularly user friendly. Performing a full library scan typically required the user to supplement the program with a wrapper script that traversed the directory tree and detected the files. rsgain's Easy Mode *is* that wrapper script; the functionality is built-in to the program. In Easy Mode, the user points the program to their library and it will be recursively scanned with all recommended settings enabled by default. The legacy-style interface has been retained as "Custom Mode" for users that require a higher level of control. Custom Mode is mostly used for scripting. ### Easy Mode Easy Mode recursively scans your entire music library using the recommended settings for each file type. Easy Mode is invoked with the command `rsgain easy` followed by the root of the directory you want to scan: ```bash rsgain easy /path/to/music/library ``` ```powershell rsgain easy "C:\path\to\music library" ``` Easy Mode assumes that you have you have your music library organized by album, so that each album is contained in its own folder. The album gain calculations rely on this assumption. If you do *not* have your music library organized by album, you should disable the album tags because the calculated values will not be valid. rsgain ships with a scan preset which can disable the album tags for you; invoke it with `-p no_album`. See the [Scan Presets](#scan-presets) section for more information about how the scan preset feature works. #### Multithreaded Scanning Easy Mode includes optional multithreaded operation to speed up the duration of a scan. Use the `-m` option, followed by the number of threads to create. The number of threads must not exceed the number that your CPU supports. For example, if you have a CPU with 4 threads: ```bash rsgain easy -m 4 /path/to/music/library ``` If you don't know how many threads your CPU has, you can also specify `-m MAX` and rsgain will use the number provided by your operating system. This is useful for writing scripts where the hardware properties of the target machine are unknown. Parallel scan jobs are generated on a *per-directory* basis, not a per-file basis. If you request 4 threads but there is only 1 directory to scan, a single thread will be working and the other 3 will sit idle the entire time. Multithreaded mode is optimized for scanning a very large number of directories. It is recommended to use multithreaded mode for full library scans and the default single threaded mode when incrementally adding 1 or 2 albums to your library. The speed gains offered by multithreaded scanning are significant. With `-m 4` or higher, you can typically expect to see a 50-80% reduction in total scan time, depending on your hardware, settings, and library composition. #### Skip Files with Existing Tags rsgain has an option which will skip files with existing ReplayGain information, invoked by passing `-S` or `--skip-existing`. When enabled, rsgain will check whether the given file has a `REPLAYGAIN_TRACK_GAIN` tag, and skip scanning any files that do. If album tags are enabled, the files in the list will be judged collectively, i.e. if a single file is missing ReplayGain info, then *all* of them will be scanned. This feature merely checks for the *existence* of the tags, and does not verify that the tags are complete, and are compatible with your current settings, e.g. target loudness. You should use this feature only if you are confident in the integrity of the files in the directory to be scanned. It's generally not a good idea to run this on files that you've recently download from the internet, which may have pre-existing ReplayGain information that was tagged by a different scanner. #### Logging You can use the `-O` option to enable scan logs. The program will save a tab-delimited file titled `replaygain.csv` with the scan results for every directory it scans. The log files can be viewed in a spreadsheet application. ##### Microsoft Excel Microsoft Excel doesn't recognize the tab delimiter in CSV files by default. To enable Excel compatibility, rsgain has an option `-Os` which will add a `sep` header to the CSV file. This is a non-standard Microsoft extension which will enable the outputted CSV files to open in Excel. ##### Sorting If you want the output sorted alphanumerically by filename, use the 'a' option, e.g. `-Oa`. The options can be chained. For example, if you want both Excel compatibility and alphanumeric sorting, you can pass `-Oas`. #### Scan Presets Easy Mode scans files with the following settings by default: - -18 LUFS target loudness - Album tags enabled - Sample peak calculations for peak tags - Clipping protection enabled for positive gain values only (0 dB max peak) - Standard uppercase tags for all formats - Preserve the existing ID3v2 tag version for ID3 formats (e.g. MP3) - Standard ReplayGain tags for Opus files These settings are recommended for maximum compatibility with modern players. However, if you need one or more of the settings changed, you can use a preset file. A preset file is an INI-formatted configuration file that contains sections enclosed in square brackets, and each section contains key=value pairs that correspond to settings. The first section in a presets file is titled "Global" and contains settings that will be applied to every format. The remaining sections pertain to a particular audio format, and the settings within them will only be applied to that format. This allows the user to define settings on a per-format basis. If a setting in the "Global" section is in conflict with one in the format-specific section, the format-specific value will always take precedence. It should be noted that the format-specific configurations will only be applied if *all* files in the directory have the same file type. The Global settings will always be applied to files in directories that have multiple file types in them, regardless of the individual file type A preset is specified with the `-p` option, followed by the path to a preset file *or* a preset name. A preset name is the filename of a preset without the directory or .ini file extension; rsgain will search the default preset locations for the file based on your platform: - In the user's home directory (you will need to create this directory if it doesn't already exist): - Windows: `%USERPROFILE%\.rsgain\presets` (typically `C:\Users\\.rsgain\presets`) - macOS: `~/Library/rsgain/presets` - Linux: `~/.config/rsgain/presets` - System location: - Windows: the folder `presets` in the same folder that contains `rsgain.exe` - macOS/Linux:`/share/rsgain/presets` For example, rsgain ships with a preset `ebur128.ini`, which will scan files based on the EBU R 128 recommendations. You can invoke this preset with `-p ebur128`. rsgain also ships with a preset `default.ini`, which is pre-populated with all of the default settings. This preset is not intended to be used directly, but rather to serve as a base for users to create their own presets. It is not recommended for users to overwrite it. Instead, save a copy when using it as a base. The settings in a preset file are applied in an "overrides" fashion. In other words, any settings or formats you're not interested in can be deleted from the preset and the defaults will be used instead. Each setting key in a presets file corresponds to a command line option in Custom Mode. Below is a table of all settings available for use in a preset. | Setting Key | Value Type | Custom Mode Option | | -------------- | ---------- | ------------------ | | TagMode | Character | -s | | TargetLoudness | Integer | -l | | Album | Boolean | -a | | ClipMode | Character | -c | | TruePeak | Boolean | -t | | Lowercase | Boolean | -L | | ID3v2Version | Integer | -I | | MaxPeakLevel | Decimal | -m | | OpusMode | Character | -o | | PreserveMtimes | Booelan | -p | See [Custom Mode](#custom-mode) for more information. #### Tag Mode `n` Easy Mode features an exclusive fourth tag mod `n` which skips files. This is distinct from the `-S` command line option in that it will skip files regardless of whether or not they have ReplayGain information, and it can be applied on a per-format basis in a preset file. This can be useful in the case that you only want to scan certain file formats. For example, suppose your Opus files are tagged with standard ReplayGain tags, but you later change your mind and want the R128_*_GAIN tags instead. Prepare a preset with the following: ```ini [Global] TagMode=n [Opus] TagMode=i OpusMode=r ``` This will skip over all files *except* Opus, so you don't waste time scanning files that you don't want to change, as would otherwise be necessary with the `s` mode. ### Custom Mode Custom Mode provides a more complex command line syntax that is similar in nature to mp3gain, loudgain, and other legacy ReplayGain scanners. Only the most basic settings are enabled by default. Unlike Easy Mode, Custom Mode works with files, not directories. Custom Mode is typically used for scripting. Custom Mode is invoked with `rsgain custom` followed by options and a list of files to scan. For example, scan and tag a short list of MP3 files with album tags enabled: ```bash rsgain custom -a -s i file1.mp3 file2.mp3 file3.mp3 ``` Run `rsgain custom -h` for a full list of available options ### MusicBrainz Picard Plugin [MusicBrainz Picard](https://picard.musicbrainz.org/) is a free, cross-platform music tagging application. Picard features a robust plugin ecosystem that greatly extends its functionality. rsgain serves as the backend for the ReplayGain 2.0 plugin, which is available from the official plugins repository. Users that prefer a graphical interface over a command line interface can use this plugin to scan their music library. To install the plugin, navigate to the Options menu in Picard. Select "Plugins" in the sidebar, then find "ReplayGain 2.0" and click the download button. The plugin itself does not include rsgain; you'll still need to download and install rsgain separately per the [Installation](#installation) section for your chosen platform. You need to set the path to rsgain in the plugin settings. This field is pre-populated with the `rsgain` command. On Unix platforms, programs are typically installed into a directory that's already in your `PATH`, so no further action is necessary in that case. On Windows, you will need to either manually add the folder containing rsgain to your `Path` as per the installation instructions, or use the exact path to `rsgain.exe` in the plugin settings. To use the plugin, add files to Picard and associate them with a release (so the files are in the right window). The plugin can scan albums or individual tracks. Select one or more albums or tracks, then right click and select "Plugins->Calculate ReplayGain" from the context menu. This calculates the ReplayGain information for the selected items, but does not tag the files. The new tags are available for viewing in the metadata window at the bottom. Click the save button to write the new tags to file. ## Design Philosophy This section provides a brief overview of modern audio theories, how they influenced the design of rsgain, and how rsgain differs from other popular ReplayGain scanners. ### What is Loudness? Loudness can be defined as the *subjective* perception of sound pressure. The subjective nature of loudness presents challenges in prescribing normalization techniques. #### Units Sound is often measured in **decibels**, or dB for short. This is because there is an approximately logarithmic relationship between the sound pressure level and perceived loudness by the human ear. Another unit used commonly in loudness normalization is the **loudness unit**, or LU for short. An LU is equivalent in magnitude to a dB. The reason that LU was introduced was provide context distinction; decibels can be used to measure many things, but with LU we are always referring to loudness. The third common unit is **loudness units relative to full scale**, or LUFS for short. Full scale means the maximum sample value of a particular digital audio signal format. For example, a value of 32,767 is full scale in 16 bit signed integer audio. On a logarithmic scale, 0 dB/LU represents full scale, so loudness measurements that are referenced to it (LUFS) will always be negative. LUFS is the most common unit for measuring audio loudness today. ### Normalizing Loudness Early attempts at loudness normalization measured a recording based on its maximum sample value, known as the **peak**. This approach was flawed because recordings vary significantly in dynamic range. Dynamic range is the difference between the high and the low parts of an audio signal. For example, consider a song that is somewhat quiet, but has a single, very loud snare drum hit. This snare drum hit, while being very loud for a short period of time, is not representative of the overall loudness of the song due to its large dynamic range. Later attempts measured signal loudness using an averaging technique known as root mean square, or RMS for short. This proved to be a much more effective measure of signal loudness than the peak. Another advancement was made in the weighting of frequencies. The human ear is not equally sensitive to all frequencies. A mid frequency is perceived to be louder than a low or high frequency of equivalent sound pressure level. The relationship between frequency and perceived loudness is often referred to as the **Fletcher-Munson curve** or the **equal-loudness contour**. The original ReplayGain specification from 2001 combined the RMS averaging with a frequency weighting filter that compensated for the Fletcher-Munson curve. Since that time, a new industry standard [ITU-R BS.1770](https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.1770-4-201510-I!!PDF-E.pdf) has been published. It details a new loudness measurement algorithm that implements RMS averaging and frequency weighting, similar in nature to ReplayGain 1.0, but far less computationally intensive and shown in listening tests to be more accurate. This new algorithm provides the basis for the ReplayGain 2.0 specification upon which rsgain is based. #### Target Loudness The loudness measurement algorithm specified in BS.1770 has gained widespread adoption in loudness normalization since its publishing. However, one aspect that is not well agreed upon is the target loudness level that audio signals should be normalized to. Many people perceive louder audio signals to be of higher quality, so the systems that target casual listeners generally tend to opt for higher target loudness levels. Higher target loudness levels are more prone to clipping, an unwanted form of distortion, so systems that target a more serious audience generally tend to opt for lower target loudness. By default, rsgain uses the -18 LUFS target loudness value specified by ReplayGain, but users have the ability to change it with the `-l` option. The table below gives a brief summary of the target loudness levels used by various organizations to demonstrate the range of typical values. | Target Loudness | Adopters | | --------------- | ------------------------------------------ | | -14 LUFS | Spotify, YouTube Music, Amazon Prime Music | | -16 LUFS | Apple Music | | -18 LUFS | ReplayGain 2.0 | | -23 LUFS | European Broadcasting Union (EBU) | ### Sample Peak vs. True Peak The ReplayGain specification requires a scanner to tag files with peak information, which is intended for use in predicting whether an audio signal will clip. There are two common ways to calculate this value: sample peak and true peak. The sample peak is the highest value sample in the signal. In ReplayGain, the peak is unitless and normalized to a scale of 0 to 1, with 1 representing digital full scale. The sample peak has been the default peak measurement used in loudness normalization until recently. True peak is a relatively new concept. The theory pertains to how audio signals are converted from analog to digital, and then eventually from digital back to analog for listening. In the first stage (analog to digital), the sampling process does not capture the true peak of the continuous analog signal because it occurred in between samples. The sample peak value that is calculated using the digital samples is therefore inaccurate. The digital signal is then mastered with the deceptively low sample peak set to just below full scale. When it's converted back into analog during playback, the true peak from the original analog signal is reconstructed and exceeds full scale, resulting in clipping. In the case of a digital audio recording, true peak from the original analog signal has already been lost forever. The method used to calculate the "true" peak from an existing digital audio signal is called interpolation, which attempts to *approximate* the original analog signal. The digital signal is resampled at a much higher sampling rate, and the interpolation algorithm attempts to estimate what original analog signal was in between the original samples. The higher oversampling rate, the better approximation of the original analog signal. Peaks that occur in the interpolated samples are known as intersample peaks. In practice, the calculated true peak value will always be higher than the sample peak. Unlike sample peaks, the true peak can exceed full scale (1). The ReplayGain specification does not explicitly specify whether the peak should be calculated using the sample peak or true peak method, leaving the decision to the implementation. Comparing popular ReplayGain scanners, r128gain always uses sample peak, while loudgain always uses true peak. Conversely, rsgain allows the user to choose between the sample peak and true peak methods. The default is sample peak. Using true peak instead of sample peak comes at a significant performance cost. Scans using true peak will typically be 2-4x longer than otherwise equivalent sample peak scans; the oversampling interpolation process used to calculate the true peak is very computationally intensive. ### Clipping Protection Clipping is a form of distortion that occurs when a signal exceeds its maximum bound, and is generally considered undesirable for audio playback. The ReplayGain standard requires scanners to tag files with the peak information as a means of preventing clipping during playback. However, not all ReplayGain-compatible players actually implement clipping protection using the peak tags. rsgain has a clipping protection feature that attempts to prevent clipping at *scan-time* instead of during playback. It is specified with the `-c` option, with a required character argument: - `n`: No clipping protection (default in Custom Mode) - `p`: Clipping protection for positive gain values only (default in Easy Mode) - `a`: Always clip protect The clipping protection works by adjusting the calculated peak by the calculated gain value (as it would be during playback). If this "new" peak value exceeds the maximum peak (full scale by default), then the gain will be adjusted lower by the excess amount, bringing the "new" peak down to the maximum level. Adjusting the gain has the side effect of lowering the loudness of the song below the target level. If this occurs in a significant number of files, it results in an uneven distribution of loudness across your music library, which defeats the purpose of applying ReplayGain in the first place. Easy Mode outputs a useful statistic "Clip Adjustments" at the end of every scan which can help you gauge how often the clipping protection is kicking in for your current settings. Scan-time clipping protection mechanisms have drawbacks and limitations. In general clipping protection can be implemented much more effectively by the player than by the scanner. If you know your player reads the peak tags and supports clipping protection, consider disabling rsgain's clipping protection entirely. Another option is to lower your target loudness level, which will signficantly reduce the number of files which activate the clipping protection. ### Opus Files Opus files are governed by [RFC 7845](https://datatracker.ietf.org/doc/html/rfc7845), which introduced a competing loudness normalization method that is completely incompatible with ReplayGain: - The gain tags are `R128_TRACK_GAIN` and `R128_ALBUM_GAIN` instead of `REPLAYGAIN_TRACK_GAIN` and `REPLAYGAIN_ALBUM_GAIN` - Peak tags are not supported - The gain values are stored in a Q7.8 fixed point integer string, instead of the standard base-10 decimal string - The gains are referenced to -23 LUFS instead of -18 LUFS Additionally, there is also an "output gain" field in the header, which contains another volume adjustment that needs to be taken into account. To handle the complexity, rsgain has a Opus Mode setting with a 5 choice character option that determines how Opus files should be tagged: - `d`: Write standard ReplayGain tags, set header output gain to 0 - `r`: Write R128_*_GAIN tags, set header output gain to 0 - `s`: Same as 'r' above, plus the target loudness is forced to -23 LUFS for Opus files only - `t`: Write track gain to header output gain - `a`: Write album gain to header output gain Since rsgain is a *ReplayGain* scanner, the `d` mode is the default, even though the ReplayGain standard conflicts with RFC 7845. In my opinion, the authors of RFC 7845 totally overstepped their authority by specifying a format-specific loudness normalization method. Particularly egregious is the specification of a target loudness level. There is no one-size-fits-all solution for target loudness. The best value depends on the dynamic range of your music, which tends to vary by genre. Moreover, most people do not have a music library comprised entirely of a single audio format, so format-specific loudness normalization methods are inappropriate. Having Opus files play back 5 dB quieter than all other file types defeats the purpose of applying ReplayGain. Some players will automatically add a +5 dB pregain to Opus files to attempt to compensate for the difference in target loudness between the RFC 7845 normalization method and ReplayGain. foobar2000 and a few others are among those that do this. You'll need to research how your chosen player(s) handle Opus files, and adjust your settings in rsgain accordingly. If you wish to write tags that are fully compliant to RFC 7845 instead of ReplayGain 2.0, you can use the `-o` 's' option. For example, as an Easy Mode preset ```ini [Opus] OpusMode=s ``` ### Tag casing There is much to be said about uppercase versus lowercase tags. In my experience, the vast majority of *modern* players recognize the standard uppercase tags for the vast majority of file formats. rsgain will write the standard uppercase tags by default for all formats. If you do encounter a player that doesn't recognize the uppercase tags, my advice is this: inform the developers with an issue - or even submit a PR yourself - rather than contribute to the fragmentation of the ReplayGain ecosystem by legitimizing the lowercase tags. Use the `-L` lowercase tags option only when all other options have been exhausted. ## Windows Notes rsgain uses UTF-8 for Unicode, while Windows has historically used a subset of UTF-16. However, Microsoft added full UTF-8 support in [Windows 10 version 1903](https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page). Therefore, it is strongly recommended to run rsgain on Windows 10 or later to ensure compatibility with all filenames. You can still run rsgain on Windows Vista through 8.1 if you don't need Unicode support, i.e. you don't have any filenames with non-ANSI characters. Another caveat with Windows is the performance of the scan progress bar. Console output on Windows is notoriously slow compared to Unix platforms. Unfortunately, the progress bar can be a bottleneck, particularly with the default sample peak calculations. I have decided to leave it as-is, under the rationale that the performance in the single-threaded mode is less important than in [multithreaded](#multithreaded-scanning), which is unaffected by this issue since the progress bar is disabled. If you do need to tag a large number of files using the single-threaded Easy Mode or Custom Mode, pass the `-q` option to disable the progress bar, which will eliminate the bottleneck. ## License rsgain is a very heavily modified fork of [loudgain](https://github.com/Moonbase59/loudgain), and is licensed accordingly under the original 2 clause BSD license used by loudgain. rsgain-3.5.1/config/000077500000000000000000000000001463075324200142565ustar00rootroot00000000000000rsgain-3.5.1/config/PKGBUILD000066400000000000000000000014121463075324200154000ustar00rootroot00000000000000pkgname=rsgain pkgver=3.5.1 pkgrel=1 epoch= pkgdesc="ReplayGain 2.0 loudness normalizer" arch=('x86_64') url="https://github.com/complexlogic/rsgain" license=('BSD') groups=() depends=('libebur128' 'taglib' 'libavformat.so' 'libavcodec.so' 'libswresample.so' 'libavutil.so' 'fmt' 'libinih') makedepends=('cmake') checkdepends=() optdepends=() provides=('rsgain') conflicts=() replaces=() backup=() options=() install= changelog= source=("${pkgname}-${pkgver}.tar.gz::https://github.com/complexlogic/${pkgname}/archive/refs/tags/v${pkgver}.tar.gz") noextract=() md5sums=('SKIP') validpgpkeys=() build() { cd "$pkgname-$pkgver" mkdir build && cd build cmake .. -DCMAKE_INSTALL_PREFIX=/usr make } package() { cd "$pkgname-$pkgver/build" make DESTDIR="$pkgdir/" install } rsgain-3.5.1/config/cmake_uninstall.cmake.in000066400000000000000000000017531463075324200210440ustar00rootroot00000000000000if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") endif(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") foreach(file ${files}) message(STATUS "Uninstalling $ENV{DESTDIR}${file}") if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") exec_program( "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval ) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") endif(NOT "${rm_retval}" STREQUAL 0) else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") message(STATUS "File $ENV{DESTDIR}${file} does not exist.") endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") endforeach(file) rsgain-3.5.1/config/config.h.in000066400000000000000000000003511463075324200163000ustar00rootroot00000000000000#define PROJECT_NAME "@PROJECT_NAME@" #define EXECUTABLE_TITLE "@EXECUTABLE_TITLE@" #define PROJECT_VERSION "@PROJECT_VERSION@" #define PROJECT_URL "@PROJECT_HOMEPAGE_URL@" #ifndef _WIN32 #define PRESETS_DIR "@PRESETS_DIR@" #endif rsgain-3.5.1/config/presets/000077500000000000000000000000001463075324200157435ustar00rootroot00000000000000rsgain-3.5.1/config/presets/default.ini000066400000000000000000000035651463075324200201010ustar00rootroot00000000000000[Global] TagMode=i Album=true TargetLoudness=-18 ClipMode=p MaxPeakLevel=0.0 TruePeak=false Lowercase=false ID3v2Version=keep OpusMode=d PreserveMtimes=false [MP3] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #Lowercase=false #ID3v2Version=keep #PreserveMtimes=false [FLAC] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false [Ogg] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false [Opus] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #OpusMode=d #PreserveMtimes=false [M4A] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #Lowercase=false #SkipMP4=false #PreserveMtimes=false [WMA] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #Lowercase=false #PreserveMtimes=false [MP2] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #Lowercase=false #ID3v2Version=keep #PreserveMtimes=false [WAV] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #Lowercase=false #ID3v2Version=keep #PreserveMtimes=false [AIFF] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #Lowercase=false #ID3v2Version=keep #PreserveMtimes=false [Wavpack] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false [APE] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false [TAK] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false [Musepack] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false rsgain-3.5.1/config/presets/ebur128.ini000066400000000000000000000035651463075324200176450ustar00rootroot00000000000000[Global] TagMode=i Album=true TargetLoudness=-23 ClipMode=p MaxPeakLevel=-1.0 TruePeak=true Lowercase=false ID3v2Version=keep OpusMode=d PreserveMtimes=false [MP3] #TagMode=i #Album=true #TargetLoudness=-23 #ClipMode=p #MaxPeakLevel=-1.0 #TruePeak=true #Lowercase=false #ID3v2Version=keep #PreserveMtimes=false [FLAC] #TagMode=i #Album=true #TargetLoudness=-23 #ClipMode=p #MaxPeakLevel=-1.0 #TruePeak=true #PreserveMtimes=false [Ogg] #TagMode=i #Album=true #TargetLoudness=-23 #ClipMode=p #MaxPeakLevel=-1.0 #TruePeak=true #PreserveMtimes=false [Opus] #TagMode=i #Album=true #TargetLoudness=-23 #ClipMode=p #MaxPeakLevel=-1.0 #TruePeak=true #OpusMode=d #PreserveMtimes=false [M4A] #TagMode=i #Album=true #TargetLoudness=-23 #ClipMode=p #MaxPeakLevel=-1.0 #TruePeak=true #Lowercase=false #SkipMP4=false #PreserveMtimes=false [WMA] #TagMode=i #Album=true #TargetLoudness=-23 #ClipMode=p #MaxPeakLevel=-1.0 #TruePeak=true #Lowercase=false #PreserveMtimes=false [MP2] #TagMode=i #Album=true #TargetLoudness=-23 #ClipMode=p #MaxPeakLevel=-1.0 #TruePeak=true #Lowercase=false #ID3v2Version=keep #PreserveMtimes=false [WAV] #TagMode=i #Album=true #TargetLoudness=-23 #ClipMode=p #MaxPeakLevel=-1.0 #TruePeak=true #Lowercase=false #ID3v2Version=keep #PreserveMtimes=false [AIFF] #TagMode=i #Album=true #TargetLoudness=-23 #ClipMode=p #MaxPeakLevel=-1.0 #TruePeak=true #Lowercase=false #ID3v2Version=keep #PreserveMtimes=false [Wavpack] #TagMode=i #Album=true #TargetLoudness=-23 #ClipMode=p #MaxPeakLevel=-1.0 #TruePeak=true #PreserveMtimes=false [APE] #TagMode=i #Album=true #TargetLoudness=-23 #ClipMode=p #MaxPeakLevel=-1.0 #TruePeak=true #PreserveMtimes=false [TAK] #TagMode=i #Album=true #TargetLoudness=-23 #ClipMode=p #MaxPeakLevel=-1.0 #TruePeak=true #PreserveMtimes=false [Musepack] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false rsgain-3.5.1/config/presets/loudgain.ini000066400000000000000000000035361463075324200202550ustar00rootroot00000000000000[Global] TagMode=i Album=true TargetLoudness=-18 ClipMode=a MaxPeakLevel=-1.0 TruePeak=true Lowercase=true ID3v2Version=3 OpusMode=r PreserveMtimes=false [MP3] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=a #MaxPeakLevel=-1.0 #TruePeak=true #Lowercase=true #ID3v2Version=3 #PreserveMtimes=false [FLAC] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=a #MaxPeakLevel=-1.0 #TruePeak=true #PreserveMtimes=false [Ogg] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=a #MaxPeakLevel=-1.0 #TruePeak=true #PreserveMtimes=false [Opus] #TagMode=i #Album=true TargetLoudness=-23 #ClipMode=a #MaxPeakLevel=-1.0 #TruePeak=true #OpusMode=r #PreserveMtimes=false [M4A] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=a #MaxPeakLevel=-1.0 #TruePeak=true #Lowercase=true #SkipMP4=false #PreserveMtimes=false [WMA] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=a #MaxPeakLevel=-1.0 #TruePeak=true #Lowercase=true #PreserveMtimes=false [MP2] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=a #MaxPeakLevel=-1.0 #TruePeak=true #Lowercase=true #ID3v2Version=3 #PreserveMtimes=false [WAV] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=a #MaxPeakLevel=-1.0 #TruePeak=true #Lowercase=true #ID3v2Version=3 #PreserveMtimes=false [AIFF] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=a #MaxPeakLevel=-1.0 #TruePeak=true #Lowercase=true #ID3v2Version=3 #PreserveMtimes=false [Wavpack] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=a #MaxPeakLevel=-1.0 #TruePeak=true #PreserveMtimes=false [APE] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=a #MaxPeakLevel=-1.0 #TruePeak=true #PreserveMtimes=false [TAK] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=a #MaxPeakLevel=-1.0 #TruePeak=true #PreserveMtimes=false [Musepack] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false rsgain-3.5.1/config/presets/no_album.ini000066400000000000000000000035661463075324200202520ustar00rootroot00000000000000[Global] TagMode=i Album=false TargetLoudness=-18 ClipMode=p MaxPeakLevel=0.0 TruePeak=false Lowercase=false ID3v2Version=keep OpusMode=d PreserveMtimes=false [MP3] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #Lowercase=false #ID3v2Version=keep #PreserveMtimes=false [FLAC] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false [Ogg] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false [Opus] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #OpusMode=d #PreserveMtimes=false [M4A] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #Lowercase=false #SkipMP4=false #PreserveMtimes=false [WMA] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #Lowercase=false #PreserveMtimes=false [MP2] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #Lowercase=false #ID3v2Version=keep #PreserveMtimes=false [WAV] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #Lowercase=false #ID3v2Version=keep #PreserveMtimes=false [AIFF] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #Lowercase=false #ID3v2Version=keep #PreserveMtimes=false [Wavpack] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false [APE] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false [TAK] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false [Musepack] #TagMode=i #Album=true #TargetLoudness=-18 #ClipMode=p #MaxPeakLevel=0.0 #TruePeak=false #PreserveMtimes=false rsgain-3.5.1/config/rsgain.manifest.in000066400000000000000000000010541463075324200176760ustar00rootroot00000000000000 UTF-8 true rsgain-3.5.1/config/vcpkg.cmake000066400000000000000000000016521463075324200163760ustar00rootroot00000000000000message("Setting up vcpkg...") include(FetchContent) FetchContent_Declare( vcpkg GIT_REPOSITORY https://github.com/microsoft/vcpkg.git GIT_SHALLOW TRUE SOURCE_DIR ${PROJECT_BINARY_DIR} ) FetchContent_Declare( vcpkg_overlay GIT_REPOSITORY https://github.com/complexlogic/vcpkg.git GIT_TAG origin/rsgain GIT_SHALLOW TRUE SOURCE_DIR ${PROJECT_BINARY_DIR} ) FetchContent_MakeAvailable(vcpkg vcpkg_overlay) set(VCPKG_OVERLAY_PORTS "${CMAKE_BINARY_DIR}/_deps/vcpkg_overlay-src/ports") if (WIN32) if (NOT VCPKG_MANIFEST_FEATURES) set(VCPKG_MANIFEST_FEATURES ffmpeg libebur128 inih) endif () if (NOT VCPKG_OVERLAY_TRIPLETS) list(APPEND VCPKG_OVERLAY_TRIPLETS "${CMAKE_SOURCE_DIR}/config/vcpkg_triplets") endif () if (NOT VCPKG_TARGET_TRIPLET) set (VCPKG_TARGET_TRIPLET "custom-triplet") endif () endif () set(CMAKE_TOOLCHAIN_FILE "${CMAKE_BINARY_DIR}/_deps/vcpkg-src/scripts/buildsystems/vcpkg.cmake") rsgain-3.5.1/config/vcpkg_triplets/000077500000000000000000000000001463075324200173165ustar00rootroot00000000000000rsgain-3.5.1/config/vcpkg_triplets/custom-triplet.cmake000066400000000000000000000002011463075324200233040ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE x64) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_LIBRARY_LINKAGE static) set(VCPKG_BUILD_TYPE release)rsgain-3.5.1/config/versioninfo.rc.in000066400000000000000000000013031463075324200175470ustar00rootroot00000000000000VS_VERSION_INFO VERSIONINFO FILEVERSION @VERSION_M@,@VERSION_N@,@VERSION_O@,@VERSION_P@ PRODUCTVERSION @VERSION_M@,@VERSION_N@,@VERSION_O@,@VERSION_P@ { BLOCK "StringFileInfo" { BLOCK "040904b0" { VALUE "CompanyName", "complexlogic" VALUE "FileDescription", "@PROJECT_DESCRIPTION@" VALUE "FileVersion", "@PROJECT_VERSION@" VALUE "OriginalFilename", "@EXECUTABLE_TITLE@.exe" VALUE "ProductName", "@PROJECT_NAME@" VALUE "ProductVersion", "@PROJECT_VERSION@" } } BLOCK "VarFileInfo" { VALUE "Translation", 0x409, 1200 } }rsgain-3.5.1/docs/000077500000000000000000000000001463075324200137415ustar00rootroot00000000000000rsgain-3.5.1/docs/BUILDING.md000066400000000000000000000074421463075324200154670ustar00rootroot00000000000000# BUILDING rsgain builds natively on Unix and Windows, and features a cross-platform CMake build system. The following external dependencies are required: - [libebur128](https://github.com/jiixyj/libebur128) or [ebur128](https://github.com/sdroege/ebur128) - TagLib - FFmpeg, specifically the libraries: + libavformat + libavcodec + libswresample + libavutil - inih - Unix only: fmt (see [below](#fmt-library)) The Windows version uses C++23, and the Unix versions use either C++20 or C++23 depending on the compiler. As such, it requires a relatively modern compiler to build: - On Windows, Visual Studio 2022 17.7 or later is required - On Linux, use GCC 10 or later - On macOS, the latest available Xcode for your machine should work ### fmt library The features that rsgain uses from the fmt library have been integrated into the C++20 and C++23 standards, specifically the `` and `` headers. Currently, neither GCC nor Clang have full support those features, so the fmt libary is required. When they gain support, the fmt dependency will be dropped in favor of the C++ standard library. The features have been incorporated into Microsoft's C++ standard library implementation, so the fmt library is no longer required for the Windows version. ## Unix Before starting, make sure you have the development tools Git, CMake and pkg-config installed. Install the required dependencies: ### APT-based Linux (Debian, Ubuntu, Mint etc.) ```bash sudo apt install libebur128-dev libtag1-dev libavformat-dev libavcodec-dev libswresample-dev libavutil-dev libfmt-dev libinih-dev ``` ### Pacman-based Linux (Arch, Manjaro, etc.) ```bash sudo pacman -S libebur128 taglib ffmpeg fmt libinih ``` ### DNF-based Linux (Fedora) FFmpeg is in the official repos in Fedora 36 and later only. If you're on an earlier version you will need to find an alternative source. ```bash sudo dnf install libebur128-devel taglib-devel libavformat-free-devel libavcodec-free-devel libswresample-free-devel libavutil-free-devel fmt-devel inih-devel ``` ### macOS (Homebrew) ```bash brew install libebur128 taglib ffmpeg fmt inih ``` ### FreeBSD (Packages) ```bash pkg install ebur128 taglib libfmt inih ffmpeg ``` ### Building Clone the repo and create a build directory: ```bash git clone https://github.com/complexlogic/rsgain.git cd rsgain mkdir build && cd build ``` Generate the Makefile: ```bash cmake .. -DCMAKE_BUILD_TYPE=Release ``` Build and test the program: ```bash make ./rsgain ``` Optionally, install to your system directories: ```bash sudo make install ``` By default, this will install rsgain with a prefix of `/usr/local`. If you want a different prefix, re-run the CMake generation step with `-DCMAKE_INSTALL_PREFIX=prefix`. #### Deb Packages The build system includes support for .deb packages via CPack. Pass `-DPACKAGE=DEB` and `-DCMAKE_INSTALL_PREFIX=/usr` to cmake. Then, build the package with: ```bash make package ``` By default, this will build a package for the amd64 architechture. To build the package for a different architecture, pass `-DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=architecture` to cmake. ## Windows The Windows toolchain consists of Visual Studio and vcpkg in addition to Git and CMake. Before starting, make sure that Visual Studio is installed with C++ core desktop features and C++ CMake tools. The free Community Edition is sufficient. Clone the repo and create a build directory: ```bash git clone https://github.com/complexlogic/rsgain.git cd rsgain mkdir build cd build ``` Build the dependencies and generate the Visual Studio project files: ```bash cmake .. -DVCPKG=ON ``` Build and test the program: ```bash cmake --build . --config Release .\Release\rsgain.exe ``` Optionally, generate a zipped install package: ```bash cmake --build . --config Release --target package ``` rsgain-3.5.1/docs/rsgain.1000066400000000000000000000113271463075324200153120ustar00rootroot00000000000000.TH "rsgain" "1" "October 2023" "" "" . .SH "NAME" \fBrsgain\fR \- ReplayGain 2\.0 loudness normalizer . .SH "SYNOPSIS" rsgain [OPTIONS] \.\.\. . .SH "DESCRIPTION" \fBrsgain\fR (really simple gain) is a ReplayGain 2\.0 command-line utility\. .P It applies loudness metadata tags to audio and video files while leaving the audio stream untouched\. .P A ReplayGain-compatible player will dynamically adjust the volume of your tagged files during playback\. .P \fBrsgain\fR supports writing tags to the following file types: .br \- AIFF (\.aiff, \.aif, \.snd) \- APE (\.ape) \- FLAC (\.flac) \- MP2 (\.mp2) \- MP3 (\.mp3) \- MP4 (\.m4a) \- Musepack (\.mpc) \- Ogg (\.ogg, \.oga, \.spx) \- Opus (\.opus) \- TAK (\.tak) \- WAV (\.wav) \- WavPack (\.wv) \- WMA (\.wma)\. . .SS "OPTIONS AND COMMANDS" .TP \fB\-h\fR, \fB\-\-help\fR Show help\. .TP \fB\-v\fR, \fB\-\-version\fR Show version number\. .TP \fBeasy\fR Easy Mode: .br Recursively scan a directory with recommended settings\. .TP \fBcustom\fR Custom Mode: .br Scan individual files with custom settings\. .P Run \fBrsgain easy \-\-help\fR or \fBrsgain custom \-\-help\fR for more information\. . .SH "EASY MODE" Usage: rsgain easy [OPTIONS] DIRECTORY .P Easy Mode recursively scans a directory using the recommended settings for each file type\. .P Easy Mode assumes that you have your music library organized with each album in its own folder\. . .SS "OPTIONS" .TP \fB\-h\fR, \fB\-\-help\fR Show help\. .TP \fB\-q\fR, \fB\-\-quiet\fR Don't print scanning status messages\. .TP \fB\-S\fR, \fB\-\-skip\-existing\fR Don't scan files with existing ReplayGain information\. .TP \fB\-m n\fR, \fB\-\-multithread=n\fR Scan files with \fBn\fR parallel threads\. .TP \fB\-p s\fR, \fB\-\-preset=s\fR Load scan preset \fBs\fR\. .TP \fB\-O\fR, \fB\-\-output\fR Output tab\-delimited scan data to CSV file per directory\. .TP \fB\-O s\fR, \fB\-\-output=s\fR Output with sep header (needed for Microsoft Excel compatibility)\. .TP \fB\-O a\fR, \fB\-\-output=a\fR Output with files sorted in alphanumeric order\. . .SH "CUSTOM MODE" Usage: rsgain custom [OPTIONS] FILES\.\.\. .P Custom Mode allows the user to specify the options to scan files with\. .P The list of files to scan must be listed explicitly after the options\. . .SS "OPTIONS" .TP \fB\-h\fR, \fB\-\-help\fR Show help\. .TP \fB\-a\fR, \fB\-\-album\fR Calculate album gain and peak\. .TP \fB\-S\fR, \fB\-\-skip\-existing\fR Don't scan files with existing ReplayGain information\. .TP \fB\-s s\fR, \fB\-\-tagmode=s\fR Scan files but don't write ReplayGain tags (default)\. .TP \fB\-s d\fR, \fB\-\-tagmode=d\fR Delete ReplayGain tags from files\. .TP \fB\-s i\fR, \fB\-\-tagmode=i\fR Scan and write ReplayGain 2\.0 tags to files\. .TP \fB-l n\fR, \fB\-\-loudness=n\fR Use \fBn\fR LUFS as target loudness (-30 ≤ n ≤ -5)\. .TP \fB\-c n\fR, \fB\-\-clip-mode=n\fR No clipping protection (default)\. .TP \fB\-c p\fR, \fB\-\-clip\-mode=p\fR Clipping protection enabled for positive\-gain values only\. .TP \fB\-c a\fR, \fB\-\-clip\-mode=a\fR Clipping protection always enabled\. .TP \fB\-m n\fR, \fB\-\-max\-peak=n\fR Use max peak level \fBn\fR dB for clipping protection\. .TP \fB\-t\fR, \fB\-\-true\-peak\fR Use true peak for peak calculations\. .TP \fB\-L\fR, \fB\-\-lowercase\fR Write lowercase tags (MP2/MP3/MP4/WMA/WAV/AIFF)\. .br This is non\-standard but sometimes needed\. .TP \fB\-I keep\fR, \fB\-\-id3v2\-version=keep\fR Keep file's existing ID3v2 version, 3 if none exists (default)\. .TP \fB\-I 3\fR, \fB\-\-id3v2\-version=3\fR Write ID3v2\.3 tags to MP2/MP3/WAV/AIFF\. .TP \fB\-I 4\fR, \fB\-\-id3v2\-version=4\fR Write ID3v2\.4 tags to MP2/MP3/WAV/AIFF\. .TP \fB\-o d\fR, \fB\-\-opus\-mode=d\fR Write standard ReplayGain tags, clear header output gain (default)\. .TP \fB\-o r\fR, \fB\-\-opus\-mode=r\fR Write R128_*_GAIN tags, clear header output gain\. .TP \fB\-o s\fR, \fB\-\-opus\-mode=s\fR Same as 'r', plus override target loudness to \-23 LUFS\. .TP \fB\-o t\fR, \fB\-\-opus\-mode=t\fR Write track gain to header output gain\. .TP \fB\-o a\fR, \fB\-\-opus\-mode=a\fR Write album gain to header output gain\. .TP \fB\-O\fR, \fB\-\-output\fR Output tab\-delimited scan data to stdout\. .TP \fB\-O s\fR, \fB\-\-output=s\fR Output with sep header (needed for Microsoft Excel compatibility)\. .TP \fB\-O a\fR, \fB\-\-output=a\fR Output with files sorted in alphanumeric order\. .TP \fB\-p\fR, \fB\-\-preserve-mtimes\fR Preserve file mtimes\. .TP \fB\-q\fR, \fB\-\-quiet\fR Don't print scanning status messages\. . .SH "BUGS" \fBrsgain\fR is maintained on GitHub. Please report all bugs to the issue tracker at https://github\.com/complexlogic/rsgain/issues\. . .SH "COPYRIGHT" Copyright (C) 2023 Hugh McMaster . .P \fBrsgain\fR is released under the BSD\-2\-Clause licence\. rsgain-3.5.1/src/000077500000000000000000000000001463075324200136005ustar00rootroot00000000000000rsgain-3.5.1/src/CMakeLists.txt000066400000000000000000000042721463075324200163450ustar00rootroot00000000000000set(SOURCE_FILES rsgain.cpp rsgain.hpp scan.cpp scan.hpp output.cpp output.hpp tag.cpp tag.hpp easymode.cpp easymode.hpp ) if (WIN32) add_executable(${EXECUTABLE_TITLE} ${SOURCE_FILES} "${PROJECT_BINARY_DIR}/rsgain.manifest" "${PROJECT_BINARY_DIR}/versioninfo.rc") target_compile_options(${EXECUTABLE_TITLE} PUBLIC "/Zc:preprocessor") target_include_directories(${EXECUTABLE_TITLE} PUBLIC ${FFMPEG_INCLUDE_DIR} ${TAGLIB_INCLUDE_DIR} ${LIBEBUR128_INCLUDE_DIR} ${FMT_INCLUDE_DIR} ${GETOPT_INCLUDE_DIR} ${INIH_INCLUDE_DIR} ) target_link_libraries(${EXECUTABLE_TITLE} ${LIBAVFORMAT} ${LIBAVCODEC} ${LIBAVUTIL} ${LIBSWRESAMPLE} ${TAGLIB} ${LIBEBUR128} ${GETOPT} ${INIH} FDK-AAC::fdk-aac ) if (VCPKG_TARGET_TRIPLET STREQUAL "custom-triplet") target_link_libraries(${EXECUTABLE_TITLE} ${STATIC_LIBS}) endif () add_compile_definitions(_CRT_SECURE_NO_WARNINGS) elseif (UNIX) add_executable(${EXECUTABLE_TITLE} ${SOURCE_FILES}) if(NOT UCHECKMARKS) target_compile_definitions(${EXECUTABLE_TITLE} PUBLIC "NOUCHECKMARKS") endif() target_link_libraries(${EXECUTABLE_TITLE} PkgConfig::LIBAVFORMAT PkgConfig::LIBAVCODEC PkgConfig::LIBSWRESAMPLE PkgConfig::LIBAVUTIL PkgConfig::TAGLIB PkgConfig::LIBEBUR128 PkgConfig::INIH Threads::Threads ) if (NOT USE_STD_FORMAT) target_link_libraries(${EXECUTABLE_TITLE} PkgConfig::FMT) endif () if (STRIP) add_custom_command(TARGET ${EXECUTABLE_TITLE} POST_BUILD COMMAND "${STRIP}" "${PROJECT_BINARY_DIR}/${EXECUTABLE_TITLE}" ) endif () if (INSTALL_MANPAGE) add_custom_command(TARGET ${EXECUTABLE_TITLE} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "${PROJECT_SOURCE_DIR}/docs/${EXECUTABLE_TITLE}.1" "${PROJECT_BINARY_DIR}" COMMAND "${GZIP}" -f "${PROJECT_BINARY_DIR}/${EXECUTABLE_TITLE}.1" ) endif () endif() set (EXECUTABLE_OUTPUT_PATH "${PROJECT_BINARY_DIR}") string(TIMESTAMP BUILD_DATE "%Y-%m-%d") add_compile_definitions("BUILD_DATE=\"${BUILD_DATE}\"") if (MAXPROGBARWIDTH GREATER_EQUAL 20) target_compile_definitions(${EXECUTABLE_TITLE} PUBLIC "MAXPROGBARWIDTH=${MAXPROGBARWIDTH}") endif () rsgain-3.5.1/src/easymode.cpp000066400000000000000000000644501463075324200161230ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #endif #include #include #include #include "rsgain.hpp" #include "easymode.hpp" #include "output.hpp" #include "scan.hpp" #define MAX_THREAD_SLEEP 30 #define HELP_STATS(title, format, ...) print(COLOR_YELLOW "{:<18} " COLOR_OFF format "\n", title ":" __VA_OPT__(,) __VA_ARGS__) extern "C" { int format_handler(void *user, const char *section, const char *name, const char *value); int global_handler(void *user, const char *section, const char *name, const char *value); } static inline void help_easy(); bool multithread = false; static Config configs[] = { // Default config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // MP2 config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // MP3 config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // FLAC config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // OGG config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // OPUS config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // M4A config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // WMA config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // WAV config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // AIFF config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // Wavpack config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // APE config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // TAK config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false }, // Musepack config { .tag_mode = 'i', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'p', .do_album = true, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false } }; const Config& get_config(FileType type) { return configs[static_cast(type)]; } // Parse Easy Mode command line arguments void easy_mode(int argc, char *argv[]) { int rc, i; char *preset = nullptr; const char *short_opts = "+hqSl:m:p:O::"; unsigned int threads = 1; opterr = 0; static struct option long_opts[] = { { "help", no_argument, nullptr, 'h' }, { "quiet", no_argument, nullptr, 'q' }, { "skip-existing", no_argument, nullptr, 'S' }, { "multithread", required_argument, nullptr, 'm' }, { "preset", required_argument, nullptr, 'p' }, { "output", optional_argument, nullptr, 'O' }, { 0, 0, 0, 0 } }; while ((rc = getopt_long(argc, argv, short_opts, long_opts, &i)) != -1) { switch (rc) { case 'h': help_easy(); quit(EXIT_SUCCESS); break; case 'q': quiet = true; break; case 'S': for (Config &config : configs) config.skip_existing = true; break; case 'm': { unsigned int max_threads = std::thread::hardware_concurrency(); if (!max_threads) max_threads = 1; if (MATCH(optarg, "MAX") || MATCH(optarg, "max")) { threads = max_threads; } else { threads = (unsigned int) (strtoul(optarg, nullptr, 10)); if (threads < 1) { output_fail("Invalid multithread argument '{}'", optarg); quit(EXIT_FAILURE); } else if (threads > max_threads) { output_warn("{} threads were requested, but only {} are available", threads, max_threads); threads = max_threads; } } multithread = (threads > 1); } break; case 'p': if (preset == nullptr) preset = optarg; break; case 'O': if (optarg) { const auto& [sep_header, sort_alphanum] = parse_output_mode(optarg); for (Config &config : configs) { config.sep_header = sep_header; config.sort_alphanum = sort_alphanum; } } for (Config &config : configs) config.tab_output = OutputType::FILE; break; case '?': if (optopt) output_fail("Unrecognized option '{:c}'", optopt); else output_fail("Unrecognized option '{}'", argv[optind - 1] + 2); quit(EXIT_FAILURE); } } if (argc == optind) { output_fail("No directory specified"); quit(EXIT_FAILURE); } scan_easy(argv[optind], preset ? preset : std::filesystem::path(), threads); } static bool convert_bool(const char *value, bool &setting) { if(MATCH(value, "True") || MATCH(value, "true")) { setting = true; return true; } if (MATCH(value, "False") || MATCH(value, "false")) { setting = false; return true; } output_fail("'{}' is not a valid boolean", value); return false; } static FileType determine_section_type(const std::string §ion) { static const std::unordered_map map = { {"MP2", FileType::MP2}, {"MP3", FileType::MP3}, {"FLAC", FileType::FLAC}, {"Ogg", FileType::OGG}, {"Opus", FileType::OPUS}, {"M4A", FileType::M4A}, {"WMA", FileType::WMA}, {"WAV", FileType::WAV}, {"AIFF", FileType::AIFF}, {"Wavpack", FileType::WAVPACK}, {"APE", FileType::APE}, {"TAK", FileType::TAK}, {"Musepack", FileType::MPC} }; auto it = map.find(section); return it == map.end() ? FileType::INVALID : it->second; } // Callback for INI parser int global_handler([[maybe_unused]] void *user, const char *section, const char *name, const char *value) { if (strcmp(section, "Global")) return 0; // Parse setting keys if (MATCH(name, "Album")) { bool do_album; if(convert_bool(value, do_album)) { for (Config &config : configs) config.do_album = do_album; } else quit(EXIT_FAILURE); } else if (MATCH(name, "TagMode")) { char tag_mode; if (parse_tag_mode_easy(value, tag_mode)) { for (Config &config : configs) config.tag_mode = tag_mode; } else quit(EXIT_FAILURE); } else if (MATCH(name, "ClipMode")) { char clip_mode; if (parse_clip_mode(value, clip_mode)) { for (Config &config : configs) config.clip_mode = clip_mode; } else quit(EXIT_FAILURE); } else if (MATCH(name, "TargetLoudness")) { double target_loudness; if (parse_target_loudness(value, target_loudness)) { for (Config &config : configs) config.target_loudness = target_loudness; } else quit(EXIT_FAILURE); } else if (MATCH(name, "MaxPeakLevel")) { double max_peak_level; if (parse_max_peak_level(value, max_peak_level)) { for (Config &config : configs) config.max_peak_level = max_peak_level; } else quit(EXIT_FAILURE); } else if (MATCH(name, "TruePeak")) { bool true_peak; if (convert_bool(value, true_peak)) { for (Config &config : configs) config.true_peak = true_peak; } else quit(EXIT_FAILURE); } else if (MATCH(name, "Lowercase")) { bool lowercase; if (convert_bool(value, lowercase)) { for (Config &config : configs) config.lowercase = lowercase; } else quit(EXIT_FAILURE); } else if (MATCH(name, "ID3v2Version")) { unsigned int id3v2version; if (parse_id3v2_version(value, id3v2version)) { for (Config &config : configs) config.id3v2version = id3v2version; } else quit(EXIT_FAILURE); } else if (MATCH(name, "OpusMode")) { char opus_mode; if (parse_opus_mode(value, opus_mode)) { for (Config &config : configs) config.opus_mode = opus_mode; } else quit(EXIT_FAILURE); } else if (MATCH(name, "PreserveMtimes")) { bool preserve_mtimes; if (convert_bool(value, preserve_mtimes)) { for (Config &config : configs) config.preserve_mtimes = preserve_mtimes; } else quit(EXIT_FAILURE); } return 0; } int format_handler([[maybe_unused]] void *user, const char *section, const char *name, const char *value) { FileType file_type = determine_section_type(section); if (file_type == FileType::INVALID) return 0; // Parse setting keys if (MATCH(name, "Album")) convert_bool(value, configs[static_cast(file_type)].do_album); else if (MATCH(name, "TagMode")) parse_tag_mode_easy(value, configs[static_cast(file_type)].tag_mode); else if (MATCH(name, "ClipMode")) parse_clip_mode(value, configs[static_cast(file_type)].clip_mode); else if (MATCH(name, "Lowercase")) convert_bool(value, configs[static_cast(file_type)].lowercase); else if (MATCH(name, "ID3v2Version")) parse_id3v2_version(value, configs[static_cast(file_type)].id3v2version); else if (MATCH(name, "TargetLoudness")) parse_target_loudness(value, configs[static_cast(file_type)].target_loudness); else if (MATCH(name, "MaxPeakLevel")) parse_max_peak_level(value, configs[static_cast(file_type)].max_peak_level); else if (MATCH(name, "TruePeak")) convert_bool(value, configs[static_cast(file_type)].true_peak); else if (MATCH(name, "OpusMode")) parse_opus_mode(value, configs[static_cast(file_type)].opus_mode); else if (file_type == FileType::M4A && MATCH(name, "SkipMP4")) convert_bool(value, configs[static_cast(file_type)].skip_mp4); else if (MATCH(name, "PreserveMtimes")) convert_bool(value, configs[static_cast(file_type)].preserve_mtimes); return 0; } inline bool join_path([[maybe_unused]] const std::filesystem::path &p) { return true; } template inline bool join_path(std::filesystem::path &path, const std::filesystem::path &first, const Args&... args) { path /= first; return join_path(path, args...); } template inline std::filesystem::path join_paths(const std::filesystem::path &first, const Args&... args) { std::filesystem::path path(first); return join_path(path, args...) ? path : std::filesystem::path(); } static void load_preset(const std::filesystem::path &preset) { std::filesystem::path path(preset); // Find preset file from name if (!path.has_extension()) { std::filesystem::path preset_basename(std::move(path)); preset_basename += ".ini"; // Check user directory #ifdef _WIN32 char buffer[1024]; if (GetEnvironmentVariableA("USERPROFILE", buffer, sizeof(buffer))) path = join_paths(buffer, "." EXECUTABLE_TITLE, "presets", preset_basename); #else const char* const home = getenv("HOME"); if (home) #ifdef __APPLE__ path = join_paths(home, "Library", EXECUTABLE_TITLE, "presets", preset_basename); #else path = join_paths(home, ".config", EXECUTABLE_TITLE, "presets", preset_basename); #endif #endif // Check .exe preset folder on Windows, system directory on Unix if (!std::filesystem::exists(path)) { #ifdef _WIN32 if (GetModuleFileNameA(nullptr, buffer, sizeof(buffer))) { std::filesystem::path exe(buffer); path = join_paths(exe.parent_path(), "presets", preset_basename); } #else path = join_paths(PRESETS_DIR, preset_basename); #endif } } if (!std::filesystem::exists(path)) { output_error("Could not locate preset '{}'", preset.string()); quit(EXIT_FAILURE); } // Parse file std::FILE *file = fopen(path.string().c_str(), "r"); if (!file) { output_error("Failed to open preset from '{}'", path.string()); quit(EXIT_FAILURE); } output_ok("Applying preset '{}'...", preset.string()); ini_parse_file(file, global_handler, nullptr); rewind(file); ini_parse_file(file, format_handler, nullptr); fclose(file); } bool WorkerThread::place_job(std::unique_ptr &job) { std::unique_lock lock(mutex, std::try_to_lock); if (!lock.owns_lock()) return false; this->job = std::move(job); job_available = true; cv.notify_all(); return true; } void WorkerThread::work() { std::unique_lock lock(mutex); { std::scoped_lock main_lock(main_mutex); main_cv.notify_all(); } while (!quit) { if (job_available) { job->scan(&ffmpeg_mutex); // Update statistics { std::scoped_lock main_lock(main_mutex); job->update_data(data); } job_available = false; main_cv.notify_all(); } // Wait until we get a new job from the main thread cv.wait_for(lock, std::chrono::seconds(MAX_THREAD_SLEEP)); } } bool WorkerThread::wait() { { std::unique_lock lock(mutex, std::try_to_lock); if (!lock.owns_lock()) return false; quit = true; cv.notify_all(); } thread->join(); return true; } void scan_easy(const std::filesystem::path &path, const std::filesystem::path &preset, size_t nb_threads) { std::queue> jobs; ScanData data; // Verify directory exists and is valid if (!std::filesystem::exists(path)) { output_fail("Directory '{}' does not exist", path.string()); quit(EXIT_FAILURE); } else if (!std::filesystem::is_directory(path)) { output_fail("'{}' is not a valid directory", path.string()); quit(EXIT_FAILURE); } // Load scan preset if (!preset.empty()) load_preset(preset); // Record start time const auto start_time = std::chrono::system_clock::now(); // Generate queue of all directories in directory tree output_ok("Building directory tree..."); std::queue directories; directories.emplace(path); for (const std::filesystem::directory_entry &entry : std::filesystem::recursive_directory_iterator(path)) { if (entry.is_directory()) directories.emplace(entry.path()); } size_t nb_directories = directories.size(); output_ok("Found {:L} {}...", nb_directories, nb_directories > 1 ? "directories" : "directory"); output_ok("Scanning {} for files...", nb_directories > 1 ? "directories" : "directory"); ScanJob *job; while(!directories.empty()) { if ((job = ScanJob::factory(directories.front()))) jobs.emplace(job); directories.pop(); } size_t nb_jobs = jobs.size(); if (nb_threads > nb_jobs) nb_threads = nb_jobs; // Mulithreaded scanning if (nb_threads > 1) { MTProgress progress(nb_jobs); std::vector> threads; std::mutex ffmpeg_mutex; std::mutex mutex; std::condition_variable cv; std::unique_lock lock(mutex); // Spawn worker threads output_ok("Scanning with {} threads...", nb_threads); for (size_t i = 0; i < nb_threads; i++) { progress.update(jobs.front()->path.string()); threads.emplace_back(std::make_unique( jobs.front(), mutex, ffmpeg_mutex, cv, data )); cv.wait_for(lock, std::chrono::milliseconds(200)); jobs.pop(); } // Feed jobs to workers std::string current_job; if (!jobs.empty()) current_job = jobs.front()->path.string(); while (!jobs.empty()) { cv.wait_for(lock, std::chrono::milliseconds(200)); for (auto &thread : threads) { if (thread->place_job(jobs.front())) { jobs.pop(); progress.update(current_job); if (!jobs.empty()) current_job = jobs.front()->path.string(); break; } } } cv.wait_for(lock, std::chrono::milliseconds(200)); // All jobs have been placed, wait for threads to finish scanning while (1) { for (auto thread = threads.begin(); thread != threads.end();) thread = (*thread)->wait() ? threads.erase(thread) : thread + 1; if (threads.empty()) break; cv.wait_for(lock, std::chrono::milliseconds(200)); } print("\33[2K\n"); } // Single threaded scanning else { while (!jobs.empty()) { auto &job = jobs.front(); job->scan(); job->update_data(data); jobs.pop(); } print("\n"); } // Output statistics at the end auto duration = std::chrono::floor(std::chrono::system_clock::now() - start_time); if (!data.files) { if (data.skipped) print("Skipped {:L} file{} with existing ReplayGain information\n", data.skipped, data.skipped > 1 ? "s" : "" ); print("No files were scanned\n"); return; } print(COLOR_GREEN "Scanning Complete" COLOR_OFF "\n"); HELP_STATS("Time Elapsed", "{:%H:%M:%S}", duration); HELP_STATS("Files Scanned", "{:L}", data.files); if (data.skipped) HELP_STATS("Files Skipped", "{:L}", data.skipped); HELP_STATS("Clip Adjustments", "{:L} ({:.1f}% of files)", data.clipping_adjustments, 100.f * (float) data.clipping_adjustments / (float) data.files); HELP_STATS("Average Gain", "{:.2f} dB", data.total_gain / (double) data.files); double average_peak = data.total_peak / (double) data.files; HELP_STATS("Average Peak", "{:.6f}{}", average_peak, average_peak != 0.0 ? format(" ({:.2f} dB)", 20.0 * log10(average_peak)) : ""); HELP_STATS("Negative Gains", "{:L} ({:.1f}% of files)", data.total_negative, 100.f * (float) data.total_negative / (float) data.files); HELP_STATS("Positive Gains", "{:L} ({:.1f}% of files)", data.total_positive, 100.f * (float) data.total_positive / (float) data.files); print("\n"); // Inform user of errors if (!data.error_directories.empty()) { print(COLOR_RED "There were errors while scanning the following directories:" COLOR_OFF "\n"); for (const std::string &s : data.error_directories) print("{}\n", s); print("\n"); } } static inline void help_easy() { print(COLOR_RED "Usage: " COLOR_OFF "{}{}{} easy [OPTIONS] DIRECTORY\n", COLOR_GREEN, EXECUTABLE_TITLE, COLOR_OFF); print(" Easy Mode recursively scans a directory using the recommended settings for each\n"); print(" file type. Easy Mode assumes that you have your music library organized with each album\n"); print(" in its own folder.\n"); print("\n"); print(COLOR_RED "Options:\n" COLOR_OFF); CMD_HELP("--help", "-h", "Show this help"); CMD_HELP("--quiet", "-q", "Don't print scanning status messages"); print("\n"); CMD_HELP("--skip-existing", "-S", "Don't scan files with existing ReplayGain information"); CMD_HELP("--multithread=n", "-m n", "Scan files with n parallel threads"); CMD_HELP("--preset=s", "-p s", "Load scan preset s"); print("\n"); CMD_HELP("--output", "-O", "Output tab-delimited scan data to CSV file per directory"); CMD_HELP("--output=s", "-O s", "Output with sep header (needed for Microsoft Excel compatibility)"); CMD_HELP("--output=a", "-O a", "Output with files sorted in alphanumeric order"); print("\n"); print("Please report any issues to " PROJECT_URL "/issues\n"); print("\n"); } rsgain-3.5.1/src/easymode.hpp000066400000000000000000000023061463075324200161200ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include "scan.hpp" class WorkerThread { public: WorkerThread(std::unique_ptr &initial_job, std::mutex &main_mutex, std::mutex &ffmpeg_mutex, std::condition_variable &main_cv, ScanData &data) : job(std::move(initial_job)), main_mutex(main_mutex), ffmpeg_mutex(ffmpeg_mutex), main_cv(main_cv), data(data) { thread = std::make_unique(&WorkerThread::work, this); } void work(); bool place_job(std::unique_ptr &job); bool wait(); private: std::unique_ptr job; std::mutex &main_mutex; std::mutex &ffmpeg_mutex; std::condition_variable &main_cv; ScanData &data; std::unique_ptr thread; bool quit = false; bool job_available = true; std::mutex mutex; std::condition_variable cv; }; void easy_mode(int argc, char *argv[]); void scan_easy(const std::filesystem::path &path, const std::filesystem::path &preset, size_t nb_threads); const Config& get_config(FileType type); rsgain-3.5.1/src/external/000077500000000000000000000000001463075324200154225ustar00rootroot00000000000000rsgain-3.5.1/src/external/CRC.h000066400000000000000000002460671463075324200162210ustar00rootroot00000000000000/** @file CRC.h @author Daniel Bahr @version 1.2.0.0 @copyright @parblock CRC++ Copyright (c) 2022, Daniel Bahr All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of CRC++ nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @endparblock */ /* CRC++ can be configured by setting various #defines before #including this header file: #define crcpp_uint8 - Specifies the type used to store CRCs that have a width of 8 bits or less. This type is not used in CRC calculations. Defaults to ::std::uint8_t. #define crcpp_uint16 - Specifies the type used to store CRCs that have a width between 9 and 16 bits (inclusive). This type is not used in CRC calculations. Defaults to ::std::uint16_t. #define crcpp_uint32 - Specifies the type used to store CRCs that have a width between 17 and 32 bits (inclusive). This type is not used in CRC calculations. Defaults to ::std::uint32_t. #define crcpp_uint64 - Specifies the type used to store CRCs that have a width between 33 and 64 bits (inclusive). This type is not used in CRC calculations. Defaults to ::std::uint64_t. #define crcpp_size - This type is used for loop iteration and function signatures only. Defaults to ::std::size_t. #define CRCPP_USE_NAMESPACE - Define to place all CRC++ code within the ::CRCPP namespace. #define CRCPP_BRANCHLESS - Define to enable a branchless CRC implementation. The branchless implementation uses a single integer multiplication in the bit-by-bit calculation instead of a small conditional. The branchless implementation may be faster on processor architectures which support single-instruction integer multiplication. #define CRCPP_USE_CPP11 - Define to enables C++11 features (move semantics, constexpr, static_assert, etc.). #define CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS - Define to include definitions for little-used CRCs. */ #ifndef CRCPP_CRC_H_ #define CRCPP_CRC_H_ #include // Includes CHAR_BIT #ifdef CRCPP_USE_CPP11 #include // Includes ::std::size_t #include // Includes ::std::uint8_t, ::std::uint16_t, ::std::uint32_t, ::std::uint64_t #else #include // Includes size_t #include // Includes uint8_t, uint16_t, uint32_t, uint64_t #endif #include // Includes ::std::numeric_limits #include // Includes ::std::move #ifndef crcpp_uint8 # ifdef CRCPP_USE_CPP11 /// @brief Unsigned 8-bit integer definition, used primarily for parameter definitions. # define crcpp_uint8 ::std::uint8_t # else /// @brief Unsigned 8-bit integer definition, used primarily for parameter definitions. # define crcpp_uint8 uint8_t # endif #endif #ifndef crcpp_uint16 # ifdef CRCPP_USE_CPP11 /// @brief Unsigned 16-bit integer definition, used primarily for parameter definitions. # define crcpp_uint16 ::std::uint16_t # else /// @brief Unsigned 16-bit integer definition, used primarily for parameter definitions. # define crcpp_uint16 uint16_t # endif #endif #ifndef crcpp_uint32 # ifdef CRCPP_USE_CPP11 /// @brief Unsigned 32-bit integer definition, used primarily for parameter definitions. # define crcpp_uint32 ::std::uint32_t # else /// @brief Unsigned 32-bit integer definition, used primarily for parameter definitions. # define crcpp_uint32 uint32_t # endif #endif #ifndef crcpp_uint64 # ifdef CRCPP_USE_CPP11 /// @brief Unsigned 64-bit integer definition, used primarily for parameter definitions. # define crcpp_uint64 ::std::uint64_t # else /// @brief Unsigned 64-bit integer definition, used primarily for parameter definitions. # define crcpp_uint64 uint64_t # endif #endif #ifndef crcpp_size # ifdef CRCPP_USE_CPP11 /// @brief Unsigned size definition, used for specifying data sizes. # define crcpp_size ::std::size_t # else /// @brief Unsigned size definition, used for specifying data sizes. # define crcpp_size size_t # endif #endif #ifdef CRCPP_USE_CPP11 /// @brief Compile-time expression definition. # define crcpp_constexpr constexpr #else /// @brief Compile-time expression definition. # define crcpp_constexpr const #endif #ifdef CRCPP_USE_NAMESPACE namespace CRCPP { #endif /** @brief Static class for computing CRCs. @note This class supports computation of full and multi-part CRCs, using a bit-by-bit algorithm or a byte-by-byte lookup table. The CRCs are calculated using as many optimizations as is reasonable. If compiling with C++11, the constexpr keyword is used liberally so that many calculations are performed at compile-time instead of at runtime. */ class CRC { public: // Forward declaration template struct Table; /** @brief CRC parameters. */ template struct Parameters { CRCType polynomial; ///< CRC polynomial CRCType initialValue; ///< Initial CRC value CRCType finalXOR; ///< Value to XOR with the final CRC bool reflectInput; ///< true to reflect all input bytes bool reflectOutput; ///< true to reflect the output CRC (reflection occurs before the final XOR) Table MakeTable() const; }; /** @brief CRC lookup table. After construction, the CRC parameters are fixed. @note A CRC table can be used for multiple CRC calculations. */ template struct Table { // Constructors are intentionally NOT marked explicit. Table(const Parameters & parameters); #ifdef CRCPP_USE_CPP11 Table(Parameters && parameters); #endif const Parameters & GetParameters() const; const CRCType * GetTable() const; CRCType operator[](unsigned char index) const; private: void InitTable(); Parameters parameters; ///< CRC parameters used to construct the table CRCType table[1 << CHAR_BIT]; ///< CRC lookup table }; // The number of bits in CRCType must be at least as large as CRCWidth. // CRCType must be an unsigned integer type or a custom type with operator overloads. template static CRCType Calculate(const void * data, crcpp_size size, const Parameters & parameters); template static CRCType Calculate(const void * data, crcpp_size size, const Parameters & parameters, CRCType crc); template static CRCType Calculate(const void * data, crcpp_size size, const Table & lookupTable); template static CRCType Calculate(const void * data, crcpp_size size, const Table & lookupTable, CRCType crc); template static CRCType CalculateBits(const void * data, crcpp_size size, const Parameters & parameters); template static CRCType CalculateBits(const void * data, crcpp_size size, const Parameters & parameters, CRCType crc); template static CRCType CalculateBits(const void * data, crcpp_size size, const Table & lookupTable); template static CRCType CalculateBits(const void * data, crcpp_size size, const Table & lookupTable, CRCType crc); // Common CRCs up to 64 bits. // Note: Check values are the computed CRCs when given an ASCII input of "123456789" (without null terminator) #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS static const Parameters< crcpp_uint8, 4> & CRC_4_ITU(); static const Parameters< crcpp_uint8, 5> & CRC_5_EPC(); static const Parameters< crcpp_uint8, 5> & CRC_5_ITU(); static const Parameters< crcpp_uint8, 5> & CRC_5_USB(); static const Parameters< crcpp_uint8, 6> & CRC_6_CDMA2000A(); static const Parameters< crcpp_uint8, 6> & CRC_6_CDMA2000B(); static const Parameters< crcpp_uint8, 6> & CRC_6_ITU(); static const Parameters< crcpp_uint8, 6> & CRC_6_NR(); static const Parameters< crcpp_uint8, 7> & CRC_7(); #endif static const Parameters< crcpp_uint8, 8> & CRC_8(); #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS static const Parameters< crcpp_uint8, 8> & CRC_8_EBU(); static const Parameters< crcpp_uint8, 8> & CRC_8_HDLC(); static const Parameters< crcpp_uint8, 8> & CRC_8_MAXIM(); static const Parameters< crcpp_uint8, 8> & CRC_8_WCDMA(); static const Parameters< crcpp_uint8, 8> & CRC_8_LTE(); static const Parameters & CRC_10(); static const Parameters & CRC_10_CDMA2000(); static const Parameters & CRC_11(); static const Parameters & CRC_11_NR(); static const Parameters & CRC_12_CDMA2000(); static const Parameters & CRC_12_DECT(); static const Parameters & CRC_12_UMTS(); static const Parameters & CRC_13_BBC(); static const Parameters & CRC_15(); static const Parameters & CRC_15_MPT1327(); #endif static const Parameters & CRC_16_ARC(); static const Parameters & CRC_16_BUYPASS(); static const Parameters & CRC_16_CCITTFALSE(); static const Parameters & CRC_16_MCRF4XX(); #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS static const Parameters & CRC_16_CDMA2000(); static const Parameters & CRC_16_CMS(); static const Parameters & CRC_16_DECTR(); static const Parameters & CRC_16_DECTX(); static const Parameters & CRC_16_DNP(); #endif static const Parameters & CRC_16_GENIBUS(); static const Parameters & CRC_16_KERMIT(); #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS static const Parameters & CRC_16_MAXIM(); static const Parameters & CRC_16_MODBUS(); static const Parameters & CRC_16_T10DIF(); static const Parameters & CRC_16_USB(); #endif static const Parameters & CRC_16_X25(); static const Parameters & CRC_16_XMODEM(); #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS static const Parameters & CRC_17_CAN(); static const Parameters & CRC_21_CAN(); static const Parameters & CRC_24(); static const Parameters & CRC_24_FLEXRAYA(); static const Parameters & CRC_24_FLEXRAYB(); static const Parameters & CRC_24_LTEA(); static const Parameters & CRC_24_LTEB(); static const Parameters & CRC_24_NRC(); static const Parameters & CRC_30(); #endif static const Parameters & CRC_32(); static const Parameters & CRC_32_BZIP2(); #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS static const Parameters & CRC_32_C(); #endif static const Parameters & CRC_32_MPEG2(); static const Parameters & CRC_32_POSIX(); #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS static const Parameters & CRC_32_Q(); static const Parameters & CRC_40_GSM(); static const Parameters & CRC_64(); #endif #ifdef CRCPP_USE_CPP11 CRC() = delete; CRC(const CRC & other) = delete; CRC & operator=(const CRC & other) = delete; CRC(CRC && other) = delete; CRC & operator=(CRC && other) = delete; #endif private: #ifndef CRCPP_USE_CPP11 CRC(); CRC(const CRC & other); CRC & operator=(const CRC & other); #endif template static IntegerType Reflect(IntegerType value, crcpp_uint16 numBits); template static CRCType Finalize(CRCType remainder, CRCType finalXOR, bool reflectOutput); template static CRCType UndoFinalize(CRCType remainder, CRCType finalXOR, bool reflectOutput); template static CRCType CalculateRemainder(const void * data, crcpp_size size, const Parameters & parameters, CRCType remainder); template static CRCType CalculateRemainder(const void * data, crcpp_size size, const Table & lookupTable, CRCType remainder); template static CRCType CalculateRemainderBits(unsigned char byte, crcpp_size numBits, const Parameters & parameters, CRCType remainder); }; /** @brief Returns a CRC lookup table construct using these CRC parameters. @note This function primarily exists to allow use of the auto keyword instead of instantiating a table directly, since template parameters are not inferred in constructors. @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC lookup table */ template inline CRC::Table CRC::Parameters::MakeTable() const { // This should take advantage of RVO and optimize out the copy. return CRC::Table(*this); } /** @brief Constructs a CRC table from a set of CRC parameters @param[in] params CRC parameters @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC */ template inline CRC::Table::Table(const Parameters & params) : parameters(params) { InitTable(); } #ifdef CRCPP_USE_CPP11 /** @brief Constructs a CRC table from a set of CRC parameters @param[in] params CRC parameters @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC */ template inline CRC::Table::Table(Parameters && params) : parameters(::std::move(params)) { InitTable(); } #endif /** @brief Gets the CRC parameters used to construct the CRC table @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC parameters */ template inline const CRC::Parameters & CRC::Table::GetParameters() const { return parameters; } /** @brief Gets the CRC table @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC table */ template inline const CRCType * CRC::Table::GetTable() const { return table; } /** @brief Gets an entry in the CRC table @param[in] index Index into the CRC table @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC table entry */ template inline CRCType CRC::Table::operator[](unsigned char index) const { return table[index]; } /** @brief Initializes a CRC table. @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC */ template inline void CRC::Table::InitTable() { // For masking off the bits for the CRC (in the event that the number of bits in CRCType is larger than CRCWidth) static crcpp_constexpr CRCType BIT_MASK((CRCType(1) << (CRCWidth - CRCType(1))) | ((CRCType(1) << (CRCWidth - CRCType(1))) - CRCType(1))); // The conditional expression is used to avoid a -Wshift-count-overflow warning. static crcpp_constexpr CRCType SHIFT((CHAR_BIT >= CRCWidth) ? static_cast(CHAR_BIT - CRCWidth) : 0); CRCType crc; unsigned char byte = 0; // Loop over each dividend (each possible number storable in an unsigned char) do { crc = CRC::CalculateRemainder(&byte, sizeof(byte), parameters, CRCType(0)); // This mask might not be necessary; all unit tests pass with this line commented out, // but that might just be a coincidence based on the CRC parameters used for testing. // In any case, this is harmless to leave in and only adds a single machine instruction per loop iteration. crc &= BIT_MASK; if (!parameters.reflectInput && CRCWidth < CHAR_BIT) { // Undo the special operation at the end of the CalculateRemainder() // function for non-reflected CRCs < CHAR_BIT. crc = static_cast(crc << SHIFT); } table[byte] = crc; } while (++byte); } /** @brief Computes a CRC. @param[in] data Data over which CRC will be computed @param[in] size Size of the data, in bytes @param[in] parameters CRC parameters @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC */ template inline CRCType CRC::Calculate(const void * data, crcpp_size size, const Parameters & parameters) { CRCType remainder = CalculateRemainder(data, size, parameters, parameters.initialValue); // No need to mask the remainder here; the mask will be applied in the Finalize() function. return Finalize(remainder, parameters.finalXOR, parameters.reflectInput != parameters.reflectOutput); } /** @brief Appends additional data to a previous CRC calculation. @note This function can be used to compute multi-part CRCs. @param[in] data Data over which CRC will be computed @param[in] size Size of the data, in bytes @param[in] parameters CRC parameters @param[in] crc CRC from a previous calculation @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC */ template inline CRCType CRC::Calculate(const void * data, crcpp_size size, const Parameters & parameters, CRCType crc) { CRCType remainder = UndoFinalize(crc, parameters.finalXOR, parameters.reflectInput != parameters.reflectOutput); remainder = CalculateRemainder(data, size, parameters, remainder); // No need to mask the remainder here; the mask will be applied in the Finalize() function. return Finalize(remainder, parameters.finalXOR, parameters.reflectInput != parameters.reflectOutput); } /** @brief Computes a CRC via a lookup table. @param[in] data Data over which CRC will be computed @param[in] size Size of the data, in bytes @param[in] lookupTable CRC lookup table @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC */ template inline CRCType CRC::Calculate(const void * data, crcpp_size size, const Table & lookupTable) { const Parameters & parameters = lookupTable.GetParameters(); CRCType remainder = CalculateRemainder(data, size, lookupTable, parameters.initialValue); // No need to mask the remainder here; the mask will be applied in the Finalize() function. return Finalize(remainder, parameters.finalXOR, parameters.reflectInput != parameters.reflectOutput); } /** @brief Appends additional data to a previous CRC calculation using a lookup table. @note This function can be used to compute multi-part CRCs. @param[in] data Data over which CRC will be computed @param[in] size Size of the data, in bytes @param[in] lookupTable CRC lookup table @param[in] crc CRC from a previous calculation @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC */ template inline CRCType CRC::Calculate(const void * data, crcpp_size size, const Table & lookupTable, CRCType crc) { const Parameters & parameters = lookupTable.GetParameters(); CRCType remainder = UndoFinalize(crc, parameters.finalXOR, parameters.reflectInput != parameters.reflectOutput); remainder = CalculateRemainder(data, size, lookupTable, remainder); // No need to mask the remainder here; the mask will be applied in the Finalize() function. return Finalize(remainder, parameters.finalXOR, parameters.reflectInput != parameters.reflectOutput); } /** @brief Computes a CRC. @param[in] data Data over which CRC will be computed @param[in] size Size of the data, in bits @param[in] parameters CRC parameters @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC */ template inline CRCType CRC::CalculateBits(const void * data, crcpp_size size, const Parameters & parameters) { CRCType remainder = parameters.initialValue; // Calculate the remainder on a whole number of bytes first, then call // a special-case function for the remaining bits. crcpp_size wholeNumberOfBytes = size / CHAR_BIT; if (wholeNumberOfBytes > 0) { remainder = CalculateRemainder(data, wholeNumberOfBytes, parameters, remainder); } crcpp_size remainingNumberOfBits = size % CHAR_BIT; if (remainingNumberOfBits != 0) { unsigned char lastByte = *(reinterpret_cast(data) + wholeNumberOfBytes); remainder = CalculateRemainderBits(lastByte, remainingNumberOfBits, parameters, remainder); } // No need to mask the remainder here; the mask will be applied in the Finalize() function. return Finalize(remainder, parameters.finalXOR, parameters.reflectInput != parameters.reflectOutput); } /** @brief Appends additional data to a previous CRC calculation. @note This function can be used to compute multi-part CRCs. @param[in] data Data over which CRC will be computed @param[in] size Size of the data, in bits @param[in] parameters CRC parameters @param[in] crc CRC from a previous calculation @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC */ template inline CRCType CRC::CalculateBits(const void * data, crcpp_size size, const Parameters & parameters, CRCType crc) { CRCType remainder = UndoFinalize(crc, parameters.finalXOR, parameters.reflectInput != parameters.reflectOutput); // Calculate the remainder on a whole number of bytes first, then call // a special-case function for the remaining bits. crcpp_size wholeNumberOfBytes = size / CHAR_BIT; if (wholeNumberOfBytes > 0) { remainder = CalculateRemainder(data, wholeNumberOfBytes, parameters, parameters.initialValue); } crcpp_size remainingNumberOfBits = size % CHAR_BIT; if (remainingNumberOfBits != 0) { unsigned char lastByte = *(reinterpret_cast(data) + wholeNumberOfBytes); remainder = CalculateRemainderBits(lastByte, remainingNumberOfBits, parameters, remainder); } // No need to mask the remainder here; the mask will be applied in the Finalize() function. return Finalize(remainder, parameters.finalXOR, parameters.reflectInput != parameters.reflectOutput); } /** @brief Computes a CRC via a lookup table. @param[in] data Data over which CRC will be computed @param[in] size Size of the data, in bits @param[in] lookupTable CRC lookup table @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC */ template inline CRCType CRC::CalculateBits(const void * data, crcpp_size size, const Table & lookupTable) { const Parameters & parameters = lookupTable.GetParameters(); CRCType remainder = parameters.initialValue; // Calculate the remainder on a whole number of bytes first, then call // a special-case function for the remaining bits. crcpp_size wholeNumberOfBytes = size / CHAR_BIT; if (wholeNumberOfBytes > 0) { remainder = CalculateRemainder(data, wholeNumberOfBytes, lookupTable, remainder); } crcpp_size remainingNumberOfBits = size % CHAR_BIT; if (remainingNumberOfBits != 0) { unsigned char lastByte = *(reinterpret_cast(data) + wholeNumberOfBytes); remainder = CalculateRemainderBits(lastByte, remainingNumberOfBits, parameters, remainder); } // No need to mask the remainder here; the mask will be applied in the Finalize() function. return Finalize(remainder, parameters.finalXOR, parameters.reflectInput != parameters.reflectOutput); } /** @brief Appends additional data to a previous CRC calculation using a lookup table. @note This function can be used to compute multi-part CRCs. @param[in] data Data over which CRC will be computed @param[in] size Size of the data, in bits @param[in] lookupTable CRC lookup table @param[in] crc CRC from a previous calculation @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC */ template inline CRCType CRC::CalculateBits(const void * data, crcpp_size size, const Table & lookupTable, CRCType crc) { const Parameters & parameters = lookupTable.GetParameters(); CRCType remainder = UndoFinalize(crc, parameters.finalXOR, parameters.reflectInput != parameters.reflectOutput); // Calculate the remainder on a whole number of bytes first, then call // a special-case function for the remaining bits. crcpp_size wholeNumberOfBytes = size / CHAR_BIT; if (wholeNumberOfBytes > 0) { remainder = CalculateRemainder(data, wholeNumberOfBytes, lookupTable, parameters.initialValue); } crcpp_size remainingNumberOfBits = size % CHAR_BIT; if (remainingNumberOfBits > 0) { unsigned char lastByte = *(reinterpret_cast(data) + wholeNumberOfBytes); remainder = CalculateRemainderBits(lastByte, remainingNumberOfBits, parameters, remainder); } // No need to mask the remainder here; the mask will be applied in the Finalize() function. return Finalize(remainder, parameters.finalXOR, parameters.reflectInput != parameters.reflectOutput); } /** @brief Reflects (i.e. reverses the bits within) an integer value. @param[in] value Value to reflect @param[in] numBits Number of bits in the integer which will be reflected @tparam IntegerType Integer type of the value being reflected @return Reflected value */ template inline IntegerType CRC::Reflect(IntegerType value, crcpp_uint16 numBits) { IntegerType reversedValue(0); for (crcpp_uint16 i = 0; i < numBits; ++i) { reversedValue = static_cast((reversedValue << 1) | (value & 1)); value = static_cast(value >> 1); } return reversedValue; } /** @brief Computes the final reflection and XOR of a CRC remainder. @param[in] remainder CRC remainder to reflect and XOR @param[in] finalXOR Final value to XOR with the remainder @param[in] reflectOutput true to reflect each byte of the remainder before the XOR @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return Final CRC */ template inline CRCType CRC::Finalize(CRCType remainder, CRCType finalXOR, bool reflectOutput) { // For masking off the bits for the CRC (in the event that the number of bits in CRCType is larger than CRCWidth) static crcpp_constexpr CRCType BIT_MASK = (CRCType(1) << (CRCWidth - CRCType(1))) | ((CRCType(1) << (CRCWidth - CRCType(1))) - CRCType(1)); if (reflectOutput) { remainder = Reflect(remainder, CRCWidth); } return (remainder ^ finalXOR) & BIT_MASK; } /** @brief Undoes the process of computing the final reflection and XOR of a CRC remainder. @note This function allows for computation of multi-part CRCs @note Calling UndoFinalize() followed by Finalize() (or vice versa) will always return the original remainder value: CRCType x = ...; CRCType y = Finalize(x, finalXOR, reflectOutput); CRCType z = UndoFinalize(y, finalXOR, reflectOutput); assert(x == z); @param[in] crc Reflected and XORed CRC @param[in] finalXOR Final value XORed with the remainder @param[in] reflectOutput true if the remainder is to be reflected @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return Un-finalized CRC remainder */ template inline CRCType CRC::UndoFinalize(CRCType crc, CRCType finalXOR, bool reflectOutput) { // For masking off the bits for the CRC (in the event that the number of bits in CRCType is larger than CRCWidth) static crcpp_constexpr CRCType BIT_MASK = (CRCType(1) << (CRCWidth - CRCType(1))) | ((CRCType(1) << (CRCWidth - CRCType(1))) - CRCType(1)); crc = (crc & BIT_MASK) ^ finalXOR; if (reflectOutput) { crc = Reflect(crc, CRCWidth); } return crc; } /** @brief Computes a CRC remainder. @param[in] data Data over which the remainder will be computed @param[in] size Size of the data, in bytes @param[in] parameters CRC parameters @param[in] remainder Running CRC remainder. Can be an initial value or the result of a previous CRC remainder calculation. @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC remainder */ template inline CRCType CRC::CalculateRemainder(const void * data, crcpp_size size, const Parameters & parameters, CRCType remainder) { #ifdef CRCPP_USE_CPP11 // This static_assert is put here because this function will always be compiled in no matter what // the template parameters are and whether or not a table lookup or bit-by-bit algorithm is used. static_assert(::std::numeric_limits::digits >= CRCWidth, "CRCType is too small to contain a CRC of width CRCWidth."); #else // Catching this compile-time error is very important. Sadly, the compiler error will be very cryptic, but it's // better than nothing. enum { static_assert_failed_CRCType_is_too_small_to_contain_a_CRC_of_width_CRCWidth = 1 / (::std::numeric_limits::digits >= CRCWidth ? 1 : 0) }; #endif const unsigned char * current = reinterpret_cast(data); // Slightly different implementations based on the parameters. The current implementations try to eliminate as much // computation from the inner loop (looping over each bit) as possible. if (parameters.reflectInput) { CRCType polynomial = CRC::Reflect(parameters.polynomial, CRCWidth); while (size--) { remainder = static_cast(remainder ^ *current++); // An optimizing compiler might choose to unroll this loop. for (crcpp_size i = 0; i < CHAR_BIT; ++i) { #ifdef CRCPP_BRANCHLESS // Clever way to avoid a branch at the expense of a multiplication. This code is equivalent to the following: // if (remainder & 1) // remainder = (remainder >> 1) ^ polynomial; // else // remainder >>= 1; remainder = static_cast((remainder >> 1) ^ ((remainder & 1) * polynomial)); #else remainder = static_cast((remainder & 1) ? ((remainder >> 1) ^ polynomial) : (remainder >> 1)); #endif } } } else if (CRCWidth >= CHAR_BIT) { static crcpp_constexpr CRCType CRC_WIDTH_MINUS_ONE(CRCWidth - CRCType(1)); #ifndef CRCPP_BRANCHLESS static crcpp_constexpr CRCType CRC_HIGHEST_BIT_MASK(CRCType(1) << CRC_WIDTH_MINUS_ONE); #endif // The conditional expression is used to avoid a -Wshift-count-overflow warning. static crcpp_constexpr CRCType SHIFT((CRCWidth >= CHAR_BIT) ? static_cast(CRCWidth - CHAR_BIT) : 0); while (size--) { remainder = static_cast(remainder ^ (static_cast(*current++) << SHIFT)); // An optimizing compiler might choose to unroll this loop. for (crcpp_size i = 0; i < CHAR_BIT; ++i) { #ifdef CRCPP_BRANCHLESS // Clever way to avoid a branch at the expense of a multiplication. This code is equivalent to the following: // if (remainder & CRC_HIGHEST_BIT_MASK) // remainder = (remainder << 1) ^ parameters.polynomial; // else // remainder <<= 1; remainder = static_cast((remainder << 1) ^ (((remainder >> CRC_WIDTH_MINUS_ONE) & 1) * parameters.polynomial)); #else remainder = static_cast((remainder & CRC_HIGHEST_BIT_MASK) ? ((remainder << 1) ^ parameters.polynomial) : (remainder << 1)); #endif } } } else { static crcpp_constexpr CRCType CHAR_BIT_MINUS_ONE(CHAR_BIT - 1); #ifndef CRCPP_BRANCHLESS static crcpp_constexpr CRCType CHAR_BIT_HIGHEST_BIT_MASK(CRCType(1) << CHAR_BIT_MINUS_ONE); #endif // The conditional expression is used to avoid a -Wshift-count-overflow warning. static crcpp_constexpr CRCType SHIFT((CHAR_BIT >= CRCWidth) ? static_cast(CHAR_BIT - CRCWidth) : 0); CRCType polynomial = static_cast(parameters.polynomial << SHIFT); remainder = static_cast(remainder << SHIFT); while (size--) { remainder = static_cast(remainder ^ *current++); // An optimizing compiler might choose to unroll this loop. for (crcpp_size i = 0; i < CHAR_BIT; ++i) { #ifdef CRCPP_BRANCHLESS // Clever way to avoid a branch at the expense of a multiplication. This code is equivalent to the following: // if (remainder & CHAR_BIT_HIGHEST_BIT_MASK) // remainder = (remainder << 1) ^ polynomial; // else // remainder <<= 1; remainder = static_cast((remainder << 1) ^ (((remainder >> CHAR_BIT_MINUS_ONE) & 1) * polynomial)); #else remainder = static_cast((remainder & CHAR_BIT_HIGHEST_BIT_MASK) ? ((remainder << 1) ^ polynomial) : (remainder << 1)); #endif } } remainder = static_cast(remainder >> SHIFT); } return remainder; } /** @brief Computes a CRC remainder using lookup table. @param[in] data Data over which the remainder will be computed @param[in] size Size of the data, in bytes @param[in] lookupTable CRC lookup table @param[in] remainder Running CRC remainder. Can be an initial value or the result of a previous CRC remainder calculation. @tparam CRCType Integer type for storing the CRC result @tparam CRCWidth Number of bits in the CRC @return CRC remainder */ template inline CRCType CRC::CalculateRemainder(const void * data, crcpp_size size, const Table & lookupTable, CRCType remainder) { const unsigned char * current = reinterpret_cast(data); if (lookupTable.GetParameters().reflectInput) { while (size--) { #if defined(WIN32) || defined(_WIN32) || defined(WINCE) // Disable warning about data loss when doing (remainder >> CHAR_BIT) when // remainder is one byte long. The algorithm is still correct in this case, // though it's possible that one additional machine instruction will be executed. # pragma warning (push) # pragma warning (disable : 4333) #endif remainder = static_cast((remainder >> CHAR_BIT) ^ lookupTable[static_cast(remainder ^ *current++)]); #if defined(WIN32) || defined(_WIN32) || defined(WINCE) # pragma warning (pop) #endif } } else if (CRCWidth >= CHAR_BIT) { // The conditional expression is used to avoid a -Wshift-count-overflow warning. static crcpp_constexpr CRCType SHIFT((CRCWidth >= CHAR_BIT) ? static_cast(CRCWidth - CHAR_BIT) : 0); while (size--) { remainder = static_cast((remainder << CHAR_BIT) ^ lookupTable[static_cast((remainder >> SHIFT) ^ *current++)]); } } else { // The conditional expression is used to avoid a -Wshift-count-overflow warning. static crcpp_constexpr CRCType SHIFT((CHAR_BIT >= CRCWidth) ? static_cast(CHAR_BIT - CRCWidth) : 0); remainder = static_cast(remainder << SHIFT); while (size--) { // Note: no need to mask here since remainder is guaranteed to fit in a single byte. remainder = lookupTable[static_cast(remainder ^ *current++)]; } remainder = static_cast(remainder >> SHIFT); } return remainder; } template inline CRCType CRC::CalculateRemainderBits(unsigned char byte, crcpp_size numBits, const Parameters & parameters, CRCType remainder) { // Slightly different implementations based on the parameters. The current implementations try to eliminate as much // computation from the inner loop (looping over each bit) as possible. if (parameters.reflectInput) { CRCType polynomial = CRC::Reflect(parameters.polynomial, CRCWidth); remainder = static_cast(remainder ^ byte); // An optimizing compiler might choose to unroll this loop. for (crcpp_size i = 0; i < numBits; ++i) { #ifdef CRCPP_BRANCHLESS // Clever way to avoid a branch at the expense of a multiplication. This code is equivalent to the following: // if (remainder & 1) // remainder = (remainder >> 1) ^ polynomial; // else // remainder >>= 1; remainder = static_cast((remainder >> 1) ^ ((remainder & 1) * polynomial)); #else remainder = static_cast((remainder & 1) ? ((remainder >> 1) ^ polynomial) : (remainder >> 1)); #endif } } else if (CRCWidth >= CHAR_BIT) { static crcpp_constexpr CRCType CRC_WIDTH_MINUS_ONE(CRCWidth - CRCType(1)); #ifndef CRCPP_BRANCHLESS static crcpp_constexpr CRCType CRC_HIGHEST_BIT_MASK(CRCType(1) << CRC_WIDTH_MINUS_ONE); #endif // The conditional expression is used to avoid a -Wshift-count-overflow warning. static crcpp_constexpr CRCType SHIFT((CRCWidth >= CHAR_BIT) ? static_cast(CRCWidth - CHAR_BIT) : 0); remainder = static_cast(remainder ^ (static_cast(byte) << SHIFT)); // An optimizing compiler might choose to unroll this loop. for (crcpp_size i = 0; i < numBits; ++i) { #ifdef CRCPP_BRANCHLESS // Clever way to avoid a branch at the expense of a multiplication. This code is equivalent to the following: // if (remainder & CRC_HIGHEST_BIT_MASK) // remainder = (remainder << 1) ^ parameters.polynomial; // else // remainder <<= 1; remainder = static_cast((remainder << 1) ^ (((remainder >> CRC_WIDTH_MINUS_ONE) & 1) * parameters.polynomial)); #else remainder = static_cast((remainder & CRC_HIGHEST_BIT_MASK) ? ((remainder << 1) ^ parameters.polynomial) : (remainder << 1)); #endif } } else { static crcpp_constexpr CRCType CHAR_BIT_MINUS_ONE(CHAR_BIT - 1); #ifndef CRCPP_BRANCHLESS static crcpp_constexpr CRCType CHAR_BIT_HIGHEST_BIT_MASK(CRCType(1) << CHAR_BIT_MINUS_ONE); #endif // The conditional expression is used to avoid a -Wshift-count-overflow warning. static crcpp_constexpr CRCType SHIFT((CHAR_BIT >= CRCWidth) ? static_cast(CHAR_BIT - CRCWidth) : 0); CRCType polynomial = static_cast(parameters.polynomial << SHIFT); remainder = static_cast((remainder << SHIFT) ^ byte); // An optimizing compiler might choose to unroll this loop. for (crcpp_size i = 0; i < numBits; ++i) { #ifdef CRCPP_BRANCHLESS // Clever way to avoid a branch at the expense of a multiplication. This code is equivalent to the following: // if (remainder & CHAR_BIT_HIGHEST_BIT_MASK) // remainder = (remainder << 1) ^ polynomial; // else // remainder <<= 1; remainder = static_cast((remainder << 1) ^ (((remainder >> CHAR_BIT_MINUS_ONE) & 1) * polynomial)); #else remainder = static_cast((remainder & CHAR_BIT_HIGHEST_BIT_MASK) ? ((remainder << 1) ^ polynomial) : (remainder << 1)); #endif } remainder = static_cast(remainder >> SHIFT); } return remainder; } #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS /** @brief Returns a set of parameters for CRC-4 ITU. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-4 ITU has the following parameters and check value: - polynomial = 0x3 - initial value = 0x0 - final XOR = 0x0 - reflect input = true - reflect output = true - check value = 0x7 @return CRC-4 ITU parameters */ inline const CRC::Parameters & CRC::CRC_4_ITU() { static const Parameters parameters = { 0x3, 0x0, 0x0, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-5 EPC. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-5 EPC has the following parameters and check value: - polynomial = 0x09 - initial value = 0x09 - final XOR = 0x00 - reflect input = false - reflect output = false - check value = 0x00 @return CRC-5 EPC parameters */ inline const CRC::Parameters & CRC::CRC_5_EPC() { static const Parameters parameters = { 0x09, 0x09, 0x00, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-5 ITU. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-5 ITU has the following parameters and check value: - polynomial = 0x15 - initial value = 0x00 - final XOR = 0x00 - reflect input = true - reflect output = true - check value = 0x07 @return CRC-5 ITU parameters */ inline const CRC::Parameters & CRC::CRC_5_ITU() { static const Parameters parameters = { 0x15, 0x00, 0x00, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-5 USB. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-5 USB has the following parameters and check value: - polynomial = 0x05 - initial value = 0x1F - final XOR = 0x1F - reflect input = true - reflect output = true - check value = 0x19 @return CRC-5 USB parameters */ inline const CRC::Parameters & CRC::CRC_5_USB() { static const Parameters parameters = { 0x05, 0x1F, 0x1F, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-6 CDMA2000-A. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-6 CDMA2000-A has the following parameters and check value: - polynomial = 0x27 - initial value = 0x3F - final XOR = 0x00 - reflect input = false - reflect output = false - check value = 0x0D @return CRC-6 CDMA2000-A parameters */ inline const CRC::Parameters & CRC::CRC_6_CDMA2000A() { static const Parameters parameters = { 0x27, 0x3F, 0x00, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-6 CDMA2000-B. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-6 CDMA2000-A has the following parameters and check value: - polynomial = 0x07 - initial value = 0x3F - final XOR = 0x00 - reflect input = false - reflect output = false - check value = 0x3B @return CRC-6 CDMA2000-B parameters */ inline const CRC::Parameters & CRC::CRC_6_CDMA2000B() { static const Parameters parameters = { 0x07, 0x3F, 0x00, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-6 ITU. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-6 ITU has the following parameters and check value: - polynomial = 0x03 - initial value = 0x00 - final XOR = 0x00 - reflect input = true - reflect output = true - check value = 0x06 @return CRC-6 ITU parameters */ inline const CRC::Parameters & CRC::CRC_6_ITU() { static const Parameters parameters = { 0x03, 0x00, 0x00, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-6 NR. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-6 NR has the following parameters and check value: - polynomial = 0x21 - initial value = 0x00 - final XOR = 0x00 - reflect input = false - reflect output = false - check value = 0x15 @return CRC-6 NR parameters */ inline const CRC::Parameters & CRC::CRC_6_NR() { static const Parameters parameters = { 0x21, 0x00, 0x00, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-7 JEDEC. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-7 JEDEC has the following parameters and check value: - polynomial = 0x09 - initial value = 0x00 - final XOR = 0x00 - reflect input = false - reflect output = false - check value = 0x75 @return CRC-7 JEDEC parameters */ inline const CRC::Parameters & CRC::CRC_7() { static const Parameters parameters = { 0x09, 0x00, 0x00, false, false }; return parameters; } #endif // CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS /** @brief Returns a set of parameters for CRC-8 SMBus. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-8 SMBus has the following parameters and check value: - polynomial = 0x07 - initial value = 0x00 - final XOR = 0x00 - reflect input = false - reflect output = false - check value = 0xF4 @return CRC-8 SMBus parameters */ inline const CRC::Parameters & CRC::CRC_8() { static const Parameters parameters = { 0x07, 0x00, 0x00, false, false }; return parameters; } #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS /** @brief Returns a set of parameters for CRC-8 EBU (aka CRC-8 AES). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-8 EBU has the following parameters and check value: - polynomial = 0x1D - initial value = 0xFF - final XOR = 0x00 - reflect input = true - reflect output = true - check value = 0x97 @return CRC-8 EBU parameters */ inline const CRC::Parameters & CRC::CRC_8_EBU() { static const Parameters parameters = { 0x1D, 0xFF, 0x00, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-8 HDLC (ISO/IEC 13239:2002). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-8 HDLC has the following parameters and check value: - polynomial = 0x07 - initial value = 0xFF - final XOR = 0xFF - reflect input = true - reflect output = true - check value = 0x2F @return CRC-8 HDLC parameters */ inline const CRC::Parameters & CRC::CRC_8_HDLC() { static const Parameters parameters = { 0x07, 0xFF, 0xFF, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-8 MAXIM (aka CRC-8 DOW-CRC). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-8 MAXIM has the following parameters and check value: - polynomial = 0x31 - initial value = 0x00 - final XOR = 0x00 - reflect input = true - reflect output = true - check value = 0xA1 @return CRC-8 MAXIM parameters */ inline const CRC::Parameters & CRC::CRC_8_MAXIM() { static const Parameters parameters = { 0x31, 0x00, 0x00, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-8 WCDMA. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-8 WCDMA has the following parameters and check value: - polynomial = 0x9B - initial value = 0x00 - final XOR = 0x00 - reflect input = true - reflect output = true - check value = 0x25 @return CRC-8 WCDMA parameters */ inline const CRC::Parameters & CRC::CRC_8_WCDMA() { static const Parameters parameters = { 0x9B, 0x00, 0x00, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-8 LTE. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-8 LTE has the following parameters and check value: - polynomial = 0x9B - initial value = 0x00 - final XOR = 0x00 - reflect input = false - reflect output = false - check value = 0xEA @return CRC-8 LTE parameters */ inline const CRC::Parameters & CRC::CRC_8_LTE() { static const Parameters parameters = { 0x9B, 0x00, 0x00, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-10 ITU. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-10 ITU has the following parameters and check value: - polynomial = 0x233 - initial value = 0x000 - final XOR = 0x000 - reflect input = false - reflect output = false - check value = 0x199 @return CRC-10 ITU parameters */ inline const CRC::Parameters & CRC::CRC_10() { static const Parameters parameters = { 0x233, 0x000, 0x000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-10 CDMA2000. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-10 CDMA2000 has the following parameters and check value: - polynomial = 0x3D9 - initial value = 0x3FF - final XOR = 0x000 - reflect input = false - reflect output = false - check value = 0x233 @return CRC-10 CDMA2000 parameters */ inline const CRC::Parameters & CRC::CRC_10_CDMA2000() { static const Parameters parameters = { 0x3D9, 0x3FF, 0x000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-11 FlexRay. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-11 FlexRay has the following parameters and check value: - polynomial = 0x385 - initial value = 0x01A - final XOR = 0x000 - reflect input = false - reflect output = false - check value = 0x5A3 @return CRC-11 FlexRay parameters */ inline const CRC::Parameters & CRC::CRC_11() { static const Parameters parameters = { 0x385, 0x01A, 0x000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-11 NR. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-11 NR has the following parameters and check value: - polynomial = 0x621 - initial value = 0x000 - final XOR = 0x000 - reflect input = false - reflect output = false - check value = 0x5CA @return CRC-11 NR parameters */ inline const CRC::Parameters & CRC::CRC_11_NR() { static const Parameters parameters = { 0x621, 0x000, 0x000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-12 CDMA2000. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-12 CDMA2000 has the following parameters and check value: - polynomial = 0xF13 - initial value = 0xFFF - final XOR = 0x000 - reflect input = false - reflect output = false - check value = 0xD4D @return CRC-12 CDMA2000 parameters */ inline const CRC::Parameters & CRC::CRC_12_CDMA2000() { static const Parameters parameters = { 0xF13, 0xFFF, 0x000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-12 DECT (aka CRC-12 X-CRC). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-12 DECT has the following parameters and check value: - polynomial = 0x80F - initial value = 0x000 - final XOR = 0x000 - reflect input = false - reflect output = false - check value = 0xF5B @return CRC-12 DECT parameters */ inline const CRC::Parameters & CRC::CRC_12_DECT() { static const Parameters parameters = { 0x80F, 0x000, 0x000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-12 UMTS (aka CRC-12 3GPP). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-12 UMTS has the following parameters and check value: - polynomial = 0x80F - initial value = 0x000 - final XOR = 0x000 - reflect input = false - reflect output = true - check value = 0xDAF @return CRC-12 UMTS parameters */ inline const CRC::Parameters & CRC::CRC_12_UMTS() { static const Parameters parameters = { 0x80F, 0x000, 0x000, false, true }; return parameters; } /** @brief Returns a set of parameters for CRC-13 BBC. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-13 BBC has the following parameters and check value: - polynomial = 0x1CF5 - initial value = 0x0000 - final XOR = 0x0000 - reflect input = false - reflect output = false - check value = 0x04FA @return CRC-13 BBC parameters */ inline const CRC::Parameters & CRC::CRC_13_BBC() { static const Parameters parameters = { 0x1CF5, 0x0000, 0x0000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-15 CAN. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-15 CAN has the following parameters and check value: - polynomial = 0x4599 - initial value = 0x0000 - final XOR = 0x0000 - reflect input = false - reflect output = false - check value = 0x059E @return CRC-15 CAN parameters */ inline const CRC::Parameters & CRC::CRC_15() { static const Parameters parameters = { 0x4599, 0x0000, 0x0000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-15 MPT1327. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-15 MPT1327 has the following parameters and check value: - polynomial = 0x6815 - initial value = 0x0000 - final XOR = 0x0001 - reflect input = false - reflect output = false - check value = 0x2566 @return CRC-15 MPT1327 parameters */ inline const CRC::Parameters & CRC::CRC_15_MPT1327() { static const Parameters parameters = { 0x6815, 0x0000, 0x0001, false, false }; return parameters; } #endif // CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS /** @brief Returns a set of parameters for CRC-16 ARC (aka CRC-16 IBM, CRC-16 LHA). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 ARC has the following parameters and check value: - polynomial = 0x8005 - initial value = 0x0000 - final XOR = 0x0000 - reflect input = true - reflect output = true - check value = 0xBB3D @return CRC-16 ARC parameters */ inline const CRC::Parameters & CRC::CRC_16_ARC() { static const Parameters parameters = { 0x8005, 0x0000, 0x0000, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-16 BUYPASS (aka CRC-16 VERIFONE, CRC-16 UMTS). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 BUYPASS has the following parameters and check value: - polynomial = 0x8005 - initial value = 0x0000 - final XOR = 0x0000 - reflect input = false - reflect output = false - check value = 0xFEE8 @return CRC-16 BUYPASS parameters */ inline const CRC::Parameters & CRC::CRC_16_BUYPASS() { static const Parameters parameters = { 0x8005, 0x0000, 0x0000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-16 CCITT FALSE. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 CCITT FALSE has the following parameters and check value: - polynomial = 0x1021 - initial value = 0xFFFF - final XOR = 0x0000 - reflect input = false - reflect output = false - check value = 0x29B1 @return CRC-16 CCITT FALSE parameters */ inline const CRC::Parameters & CRC::CRC_16_CCITTFALSE() { static const Parameters parameters = { 0x1021, 0xFFFF, 0x0000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-16 MCRF4XX. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 MCRF4XX has the following parameters and check value: - polynomial = 0x1021 - initial value = 0xFFFF - final XOR = 0x0000 - reflect input = true - reflect output = true - check value = 0x6F91 @return CRC-16 MCRF4XX parameters */ inline const CRC::Parameters & CRC::CRC_16_MCRF4XX() { static const Parameters parameters = { 0x1021, 0xFFFF, 0x0000, true, true}; return parameters; } #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS /** @brief Returns a set of parameters for CRC-16 CDMA2000. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 CDMA2000 has the following parameters and check value: - polynomial = 0xC867 - initial value = 0xFFFF - final XOR = 0x0000 - reflect input = false - reflect output = false - check value = 0x4C06 @return CRC-16 CDMA2000 parameters */ inline const CRC::Parameters & CRC::CRC_16_CDMA2000() { static const Parameters parameters = { 0xC867, 0xFFFF, 0x0000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-16 CMS. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 CMS has the following parameters and check value: - polynomial = 0x8005 - initial value = 0xFFFF - final XOR = 0x0000 - reflect input = false - reflect output = false - check value = 0xAEE7 @return CRC-16 CMS parameters */ inline const CRC::Parameters & CRC::CRC_16_CMS() { static const Parameters parameters = { 0x8005, 0xFFFF, 0x0000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-16 DECT-R (aka CRC-16 R-CRC). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 DECT-R has the following parameters and check value: - polynomial = 0x0589 - initial value = 0x0000 - final XOR = 0x0001 - reflect input = false - reflect output = false - check value = 0x007E @return CRC-16 DECT-R parameters */ inline const CRC::Parameters & CRC::CRC_16_DECTR() { static const Parameters parameters = { 0x0589, 0x0000, 0x0001, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-16 DECT-X (aka CRC-16 X-CRC). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 DECT-X has the following parameters and check value: - polynomial = 0x0589 - initial value = 0x0000 - final XOR = 0x0000 - reflect input = false - reflect output = false - check value = 0x007F @return CRC-16 DECT-X parameters */ inline const CRC::Parameters & CRC::CRC_16_DECTX() { static const Parameters parameters = { 0x0589, 0x0000, 0x0000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-16 DNP. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 DNP has the following parameters and check value: - polynomial = 0x3D65 - initial value = 0x0000 - final XOR = 0xFFFF - reflect input = true - reflect output = true - check value = 0xEA82 @return CRC-16 DNP parameters */ inline const CRC::Parameters & CRC::CRC_16_DNP() { static const Parameters parameters = { 0x3D65, 0x0000, 0xFFFF, true, true }; return parameters; } #endif // CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS /** @brief Returns a set of parameters for CRC-16 GENIBUS (aka CRC-16 EPC, CRC-16 I-CODE, CRC-16 DARC). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 GENIBUS has the following parameters and check value: - polynomial = 0x1021 - initial value = 0xFFFF - final XOR = 0xFFFF - reflect input = false - reflect output = false - check value = 0xD64E @return CRC-16 GENIBUS parameters */ inline const CRC::Parameters & CRC::CRC_16_GENIBUS() { static const Parameters parameters = { 0x1021, 0xFFFF, 0xFFFF, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-16 KERMIT (aka CRC-16 CCITT, CRC-16 CCITT-TRUE). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 KERMIT has the following parameters and check value: - polynomial = 0x1021 - initial value = 0x0000 - final XOR = 0x0000 - reflect input = true - reflect output = true - check value = 0x2189 @return CRC-16 KERMIT parameters */ inline const CRC::Parameters & CRC::CRC_16_KERMIT() { static const Parameters parameters = { 0x1021, 0x0000, 0x0000, true, true }; return parameters; } #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS /** @brief Returns a set of parameters for CRC-16 MAXIM. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 MAXIM has the following parameters and check value: - polynomial = 0x8005 - initial value = 0x0000 - final XOR = 0xFFFF - reflect input = true - reflect output = true - check value = 0x44C2 @return CRC-16 MAXIM parameters */ inline const CRC::Parameters & CRC::CRC_16_MAXIM() { static const Parameters parameters = { 0x8005, 0x0000, 0xFFFF, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-16 MODBUS. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 MODBUS has the following parameters and check value: - polynomial = 0x8005 - initial value = 0xFFFF - final XOR = 0x0000 - reflect input = true - reflect output = true - check value = 0x4B37 @return CRC-16 MODBUS parameters */ inline const CRC::Parameters & CRC::CRC_16_MODBUS() { static const Parameters parameters = { 0x8005, 0xFFFF, 0x0000, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-16 T10-DIF. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 T10-DIF has the following parameters and check value: - polynomial = 0x8BB7 - initial value = 0x0000 - final XOR = 0x0000 - reflect input = false - reflect output = false - check value = 0xD0DB @return CRC-16 T10-DIF parameters */ inline const CRC::Parameters & CRC::CRC_16_T10DIF() { static const Parameters parameters = { 0x8BB7, 0x0000, 0x0000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-16 USB. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 USB has the following parameters and check value: - polynomial = 0x8005 - initial value = 0xFFFF - final XOR = 0xFFFF - reflect input = true - reflect output = true - check value = 0xB4C8 @return CRC-16 USB parameters */ inline const CRC::Parameters & CRC::CRC_16_USB() { static const Parameters parameters = { 0x8005, 0xFFFF, 0xFFFF, true, true }; return parameters; } #endif // CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS /** @brief Returns a set of parameters for CRC-16 X-25 (aka CRC-16 IBM-SDLC, CRC-16 ISO-HDLC, CRC-16 B). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 X-25 has the following parameters and check value: - polynomial = 0x1021 - initial value = 0xFFFF - final XOR = 0xFFFF - reflect input = true - reflect output = true - check value = 0x906E @return CRC-16 X-25 parameters */ inline const CRC::Parameters & CRC::CRC_16_X25() { static const Parameters parameters = { 0x1021, 0xFFFF, 0xFFFF, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-16 XMODEM (aka CRC-16 ZMODEM, CRC-16 ACORN, CRC-16 LTE). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-16 XMODEM has the following parameters and check value: - polynomial = 0x1021 - initial value = 0x0000 - final XOR = 0x0000 - reflect input = false - reflect output = false - check value = 0x31C3 @return CRC-16 XMODEM parameters */ inline const CRC::Parameters & CRC::CRC_16_XMODEM() { static const Parameters parameters = { 0x1021, 0x0000, 0x0000, false, false }; return parameters; } #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS /** @brief Returns a set of parameters for CRC-17 CAN. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-17 CAN has the following parameters and check value: - polynomial = 0x1685B - initial value = 0x00000 - final XOR = 0x00000 - reflect input = false - reflect output = false - check value = 0x04F03 @return CRC-17 CAN parameters */ inline const CRC::Parameters & CRC::CRC_17_CAN() { static const Parameters parameters = { 0x1685B, 0x00000, 0x00000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-21 CAN. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-21 CAN has the following parameters and check value: - polynomial = 0x102899 - initial value = 0x000000 - final XOR = 0x000000 - reflect input = false - reflect output = false - check value = 0x0ED841 @return CRC-21 CAN parameters */ inline const CRC::Parameters & CRC::CRC_21_CAN() { static const Parameters parameters = { 0x102899, 0x000000, 0x000000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-24 OPENPGP. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-24 OPENPGP has the following parameters and check value: - polynomial = 0x864CFB - initial value = 0xB704CE - final XOR = 0x000000 - reflect input = false - reflect output = false - check value = 0x21CF02 @return CRC-24 OPENPGP parameters */ inline const CRC::Parameters & CRC::CRC_24() { static const Parameters parameters = { 0x864CFB, 0xB704CE, 0x000000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-24 FlexRay-A. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-24 FlexRay-A has the following parameters and check value: - polynomial = 0x5D6DCB - initial value = 0xFEDCBA - final XOR = 0x000000 - reflect input = false - reflect output = false - check value = 0x7979BD @return CRC-24 FlexRay-A parameters */ inline const CRC::Parameters & CRC::CRC_24_FLEXRAYA() { static const Parameters parameters = { 0x5D6DCB, 0xFEDCBA, 0x000000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-24 FlexRay-B. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-24 FlexRay-B has the following parameters and check value: - polynomial = 0x5D6DCB - initial value = 0xABCDEF - final XOR = 0x000000 - reflect input = false - reflect output = false - check value = 0x1F23B8 @return CRC-24 FlexRay-B parameters */ inline const CRC::Parameters & CRC::CRC_24_FLEXRAYB() { static const Parameters parameters = { 0x5D6DCB, 0xABCDEF, 0x000000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-24 LTE-A/NR-A. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-24 LTE-A has the following parameters and check value: - polynomial = 0x864CFB - initial value = 0x000000 - final XOR = 0x000000 - reflect input = false - reflect output = false - check value = 0xCDE703 @return CRC-24 LTE-A parameters */ inline const CRC::Parameters & CRC::CRC_24_LTEA() { static const Parameters parameters = { 0x864CFB, 0x000000, 0x000000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-24 LTE-B/NR-B. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-24 LTE-B has the following parameters and check value: - polynomial = 0x800063 - initial value = 0x000000 - final XOR = 0x000000 - reflect input = false - reflect output = false - check value = 0x23EF52 @return CRC-24 LTE-B parameters */ inline const CRC::Parameters & CRC::CRC_24_LTEB() { static const Parameters parameters = { 0x800063, 0x000000, 0x000000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-24 NR-C. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-24 NR-C has the following parameters and check value: - polynomial = 0xB2B117 - initial value = 0x000000 - final XOR = 0x000000 - reflect input = false - reflect output = false - check value = 0xF48279 @return CRC-24 NR-C parameters */ inline const CRC::Parameters & CRC::CRC_24_NRC() { static const Parameters parameters = { 0xB2B117, 0x000000, 0x000000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-30 CDMA. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-30 CDMA has the following parameters and check value: - polynomial = 0x2030B9C7 - initial value = 0x3FFFFFFF - final XOR = 0x00000000 - reflect input = false - reflect output = false - check value = 0x3B3CB540 @return CRC-30 CDMA parameters */ inline const CRC::Parameters & CRC::CRC_30() { static const Parameters parameters = { 0x2030B9C7, 0x3FFFFFFF, 0x00000000, false, false }; return parameters; } #endif // CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS /** @brief Returns a set of parameters for CRC-32 (aka CRC-32 ADCCP, CRC-32 PKZip). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-32 has the following parameters and check value: - polynomial = 0x04C11DB7 - initial value = 0xFFFFFFFF - final XOR = 0xFFFFFFFF - reflect input = true - reflect output = true - check value = 0xCBF43926 @return CRC-32 parameters */ inline const CRC::Parameters & CRC::CRC_32() { static const Parameters parameters = { 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, true, true }; return parameters; } /** @brief Returns a set of parameters for CRC-32 BZIP2 (aka CRC-32 AAL5, CRC-32 DECT-B, CRC-32 B-CRC). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-32 BZIP2 has the following parameters and check value: - polynomial = 0x04C11DB7 - initial value = 0xFFFFFFFF - final XOR = 0xFFFFFFFF - reflect input = false - reflect output = false - check value = 0xFC891918 @return CRC-32 BZIP2 parameters */ inline const CRC::Parameters & CRC::CRC_32_BZIP2() { static const Parameters parameters = { 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, false, false }; return parameters; } #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS /** @brief Returns a set of parameters for CRC-32 C (aka CRC-32 ISCSI, CRC-32 Castagnoli, CRC-32 Interlaken). @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-32 C has the following parameters and check value: - polynomial = 0x1EDC6F41 - initial value = 0xFFFFFFFF - final XOR = 0xFFFFFFFF - reflect input = true - reflect output = true - check value = 0xE3069283 @return CRC-32 C parameters */ inline const CRC::Parameters & CRC::CRC_32_C() { static const Parameters parameters = { 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, true, true }; return parameters; } #endif /** @brief Returns a set of parameters for CRC-32 MPEG-2. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-32 MPEG-2 has the following parameters and check value: - polynomial = 0x04C11DB7 - initial value = 0xFFFFFFFF - final XOR = 0x00000000 - reflect input = false - reflect output = false - check value = 0x0376E6E7 @return CRC-32 MPEG-2 parameters */ inline const CRC::Parameters & CRC::CRC_32_MPEG2() { static const Parameters parameters = { 0x04C11DB7, 0xFFFFFFFF, 0x00000000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-32 POSIX. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-32 POSIX has the following parameters and check value: - polynomial = 0x04C11DB7 - initial value = 0x00000000 - final XOR = 0xFFFFFFFF - reflect input = false - reflect output = false - check value = 0x765E7680 @return CRC-32 POSIX parameters */ inline const CRC::Parameters & CRC::CRC_32_POSIX() { static const Parameters parameters = { 0x04C11DB7, 0x00000000, 0xFFFFFFFF, false, false }; return parameters; } #ifdef CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS /** @brief Returns a set of parameters for CRC-32 Q. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-32 Q has the following parameters and check value: - polynomial = 0x814141AB - initial value = 0x00000000 - final XOR = 0x00000000 - reflect input = false - reflect output = false - check value = 0x3010BF7F @return CRC-32 Q parameters */ inline const CRC::Parameters & CRC::CRC_32_Q() { static const Parameters parameters = { 0x814141AB, 0x00000000, 0x00000000, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-40 GSM. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-40 GSM has the following parameters and check value: - polynomial = 0x0004820009 - initial value = 0x0000000000 - final XOR = 0xFFFFFFFFFF - reflect input = false - reflect output = false - check value = 0xD4164FC646 @return CRC-40 GSM parameters */ inline const CRC::Parameters & CRC::CRC_40_GSM() { static const Parameters parameters = { 0x0004820009, 0x0000000000, 0xFFFFFFFFFF, false, false }; return parameters; } /** @brief Returns a set of parameters for CRC-64 ECMA. @note The parameters are static and are delayed-constructed to reduce memory footprint. @note CRC-64 ECMA has the following parameters and check value: - polynomial = 0x42F0E1EBA9EA3693 - initial value = 0x0000000000000000 - final XOR = 0x0000000000000000 - reflect input = false - reflect output = false - check value = 0x6C40DF5F0B497347 @return CRC-64 ECMA parameters */ inline const CRC::Parameters & CRC::CRC_64() { static const Parameters parameters = { 0x42F0E1EBA9EA3693, 0x0000000000000000, 0x0000000000000000, false, false }; return parameters; } #endif // CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS #ifdef CRCPP_USE_NAMESPACE } #endif #endif // CRCPP_CRC_H_ rsgain-3.5.1/src/output.cpp000066400000000000000000000106041463075324200156450ustar00rootroot00000000000000/* * Loudness normalizer based on the EBU R128 standard * * Copyright (c) 2014, Alessandro Ghedini * All rights reserved. * * rsgain by complexlogic, 2022 * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #else #include #include #include #include #endif #include "output.hpp" #include "config.h" #define MT_MESSAGE " Scanning directory: " constexpr int str_literal_len(const char *str) { return *str ? 1 + str_literal_len(str + 1) : 0; } void ProgressBar::begin(int start, int len) { this->start = start; this->len = len; } void ProgressBar::update(int pos) { int w, c; if (pos == pos_prev || !len) return; w = get_console_width(); #ifdef MAXPROGBARWIDTH if (w > MAXPROGBARWIDTH) w = MAXPROGBARWIDTH; #endif w -= 8; if (w <= 0) return; if (w != w_prev) { delete buffer; buffer = new char[(size_t) (w + 3)]; } float percent = (float) pos / (float) len; c = (int) (percent * (float) w); if (c > w) c = w; // Only output if we've actually made progress since last the call, or the console width changed if (c != c_prev || w != w_prev) { print(" {:3.0f}% [", percent * 100.f); memset(buffer, '=', (size_t) c); memset(buffer + c, ' ', (size_t) (w - c)); buffer[w] = ']'; buffer[w + 1] = '\r'; buffer[w + 2] = '\0'; #ifdef _WIN32 WriteConsoleA(console, buffer, w + 2, nullptr, nullptr); #else fputs(buffer, stdout); fflush(stdout); #endif } c_prev = c; w_prev = w; pos_prev = pos; } void ProgressBar::complete() { if (c_prev != w_prev) update(len); delete buffer; buffer = nullptr; print("\n"); } inline int ProgressBar::get_console_width() { #ifdef _WIN32 GetConsoleScreenBufferInfo(console, &info); return info.srWindow.Right - info.srWindow.Left + 1; #else if (ioctl(fileno(stdout), TIOCGWINSZ, &ws) < 0 || !ws.ws_col) return 0; return ws.ws_col; #endif } void MTProgress::update(const std::string &path) { if (quiet) return; static constexpr int w_message = 7 + str_literal_len(MT_MESSAGE); int w_console = ProgressBar::get_console_width(); if (!w_console) return; int w_path = utf8_length(path); if (w_path + w_message >= w_console) w_path = w_console - w_message; print("\33[2K " COLOR_GREEN "{:5.1f}%" COLOR_OFF MT_MESSAGE "{:.{}}\r", 100.f * ((float) (cur) / (float) (total)), path, w_path < 0 ? 0 : w_path ); fflush(stdout); cur++; } int MTProgress::utf8_length(std::string_view string) { int length = 0; auto it = string.cbegin(); while (it < string.cend()) { if ((*it & 0x80) == 0) // If byte is 0xxxxxxx, then it's a 1 byte (ASCII) char it++; else if ((*it & 0xE0) == 0xC0) // If byte is 110xxxxx, then it's a 2 byte char it +=2; else if ((*it & 0xF0) == 0xE0) // If byte is 1110xxxx, then it's a 3 byte char it +=3; else if ((*it & 0xF8) == 0xF0 || 1) // If byte is 11110xxx, then it's a 4 byte char it +=4; length++; } return length; } rsgain-3.5.1/src/output.hpp000066400000000000000000000075511463075324200156610ustar00rootroot00000000000000#pragma once /* * Loudness normalizer based on the EBU R128 standard * * Copyright (c) 2014, Alessandro Ghedini * All rights reserved. * * rsgain by complexlogic, 2022 * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef __unix__ #include #include #include #include #endif #ifdef __APPLE__ #include #endif #ifdef _WIN32 #include #endif #include #include #ifdef USE_STD_FORMAT #include #include #define format std::format #define print std::print #else #include #include #define format fmt::format #define print fmt::print #endif #define COLOR_GREEN "" #define COLOR_YELLOW "" #define COLOR_RED "" #define COLOR_BGRED "" #define COLOR_OFF "" // The default Windows console font doesn't support the ✔ and ✘ characters // and make it optional by defining NOUCHECKMARKS #if defined _WIN32 || defined NOUCHECKMARKS #define OK_CHAR "OK" #define ERROR_CHAR "ERROR" #define FAIL_CHAR "FAILURE" #else #define OK_CHAR "✔" #define ERROR_CHAR "✘" #define FAIL_CHAR "✘" #endif #define WARN_CHAR "!" #define OK_PREFIX "[" COLOR_GREEN OK_CHAR COLOR_OFF "] " #define WARN_PREFIX "[" COLOR_YELLOW WARN_CHAR COLOR_OFF "] " #define ERROR_PREFIX "[" COLOR_RED ERROR_CHAR COLOR_OFF "] " #define FAIL_PREFIX "[" COLOR_RED FAIL_CHAR COLOR_OFF "] " extern int quiet; #define output_ok(format, ...) if (!quiet) print(OK_PREFIX format "\n" __VA_OPT__(,) __VA_ARGS__) #define output_warn(format, ...) if (!quiet) print(WARN_PREFIX format "\n" __VA_OPT__(,) __VA_ARGS__) #define output_error(format, ...) print(stderr, ERROR_PREFIX format "\n" __VA_OPT__(,) __VA_ARGS__) #define output_fail(format, ...) print(stderr, FAIL_PREFIX format "\n" __VA_OPT__(,) __VA_ARGS__) class ProgressBar { private: int c_prev = -1; int w_prev = -1; int pos_prev = -1; int start; int len = 0; char *buffer = nullptr; #ifdef _WIN32 inline static CONSOLE_SCREEN_BUFFER_INFO info; inline static HANDLE console; #else inline static struct winsize ws; #endif public: static int get_console_width(); void begin(int start, int len); void update(int pos); void complete(); #ifdef _WIN32 static void set_console(HANDLE c) { console = c; } #endif }; class MTProgress { private: size_t total; int cur = 0; int utf8_length(std::string_view string); public: MTProgress(size_t total) : total(total) {} void update(const std::string &path); }; rsgain-3.5.1/src/rsgain.cpp000066400000000000000000000416401463075324200155740ustar00rootroot00000000000000/* * Loudness normalizer based on the EBU R128 standard * * Copyright (c) 2014, Alessandro Ghedini * All rights reserved. * * rsgain by complexlogic, 2022 * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include extern "C" { #include #include #include #include } #include #define HAS_TAGLIB2 TAGLIB_MAJOR_VERSION > 1 #if HAS_TAGLIB2 #include #endif #include #include "rsgain.hpp" #include "tag.hpp" #include "config.h" #include "scan.hpp" #include "output.hpp" #include "easymode.hpp" #define PRINT_LIB(lib, version) print(" " COLOR_YELLOW " {:<14}" COLOR_OFF " {}\n", lib, version) #define PRINT_LIB_FFMPEG(name, fn) \ ffver = fn(); \ PRINT_LIB(name, format("{}.{}.{}", AV_VERSION_MAJOR(ffver), AV_VERSION_MINOR(ffver), AV_VERSION_MICRO(ffver))) #ifdef _WIN32 #include void init_console(); void set_cursor_visibility(HANDLE console, BOOL setting, BOOL *previous); #endif static void help_main(); static void version(); static inline void help_custom(); int quiet = 0; #ifdef _WIN32 BOOL initial_cursor_visibility; static void init_console() { HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleMode(console, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING); set_cursor_visibility(console, FALSE, &initial_cursor_visibility); ProgressBar::set_console(console); } // Make the console cursor invisible for the progress bar static void set_cursor_visibility(HANDLE console, BOOL setting, BOOL* previous) { CONSOLE_CURSOR_INFO info; GetConsoleCursorInfo(console, &info); if (previous) *previous = info.bVisible; info.bVisible = setting; SetConsoleCursorInfo(console, &info); } #endif void quit(int status) { #ifdef _WIN32 if (initial_cursor_visibility) set_cursor_visibility(GetStdHandle(STD_OUTPUT_HANDLE), TRUE, nullptr); #endif exit(status); } bool parse_target_loudness(const char *value, double &target_loudness) { int loudness = atoi(value); if (loudness < MIN_TARGET_LOUDNESS || loudness > MAX_TARGET_LOUDNESS) { output_error("Invalid target loudness value '{}'", value); return false; } target_loudness = (double) loudness; return true; } bool parse_mode(const char *name, const char *valid_modes, const char *value, char &mode) { const std::string_view vm = valid_modes; size_t pos = vm.find_first_of(*value); if (pos != std::string::npos) { mode = valid_modes[pos]; return true; } output_error("Invalid {} mode: '{}'", name, value); return false; } bool parse_id3v2_version(const char *value, unsigned int &version) { if (MATCH(value, "keep")) { version = ID3V2_KEEP; return true; } else { unsigned int id3v2version = (unsigned int) strtoul(value, nullptr, 10); if (!(id3v2version == 3) && !(id3v2version == 4)) { output_error("Invalid ID3v2 version '{}'; only 'keep', '3', and '4' are supported.", value); return false; } version = id3v2version; return true; } } bool parse_max_peak_level(const char *value, double &peak) { char *rest = nullptr; double max_peak = strtod(value, &rest); if (rest == value || !isfinite(max_peak)) { output_error("Invalid max peak level '{}'", value); return false; } peak = max_peak; return true; } std::pair parse_output_mode(const std::string_view arg) { std::pair ret(false, false); for (char c : arg) { if (c == 's') ret.first = true; else if (c == 'a') ret.second = true; else { output_fail("Unrecognized output argument '{}'", c); quit(EXIT_FAILURE); } } return ret; } // Parse Custom Mode command line arguments static void custom_mode(int argc, char *argv[]) { int rc, i; unsigned int nb_files = 0; opterr = 0; const char *short_opts = "+ac:m:tl:O::qps:LSI:o:h?"; static struct option long_opts[] = { { "album", no_argument, nullptr, 'a' }, { "skip-existing", no_argument, nullptr, 'S' }, { "clip-mode", required_argument, nullptr, 'c' }, { "max-peak", required_argument, nullptr, 'm' }, { "true-peak", no_argument, nullptr, 't' }, { "loudness", required_argument, nullptr, 'l' }, { "output", optional_argument, nullptr, 'O' }, { "quiet", no_argument, nullptr, 'q' }, { "preserve-mtimes", no_argument, nullptr, 'p' }, { "tagmode", required_argument, nullptr, 's' }, { "lowercase", no_argument, nullptr, 'L' }, { "id3v2-version", required_argument, nullptr, 'I' }, { "opus-mode", required_argument, nullptr, 'o' }, { "help", no_argument, nullptr, 'h' }, { 0, 0, 0, 0 } }; Config config = { .tag_mode = 's', .skip_existing = false, .target_loudness = RG_TARGET_LOUDNESS, .max_peak_level = 0.0, .true_peak = false, .clip_mode = 'n', .do_album = false, .tab_output = OutputType::NONE, .sep_header = false, .sort_alphanum = false, .lowercase = false, .id3v2version = ID3V2_KEEP, .opus_mode = 'd', .skip_mp4 = false, .preserve_mtimes = false }; while ((rc = getopt_long(argc, argv, short_opts, long_opts, &i)) != -1) { switch (rc) { case 'a': config.do_album = true; break; case 'S': config.skip_existing = true; break; case 'c': if (!parse_clip_mode(optarg, config.clip_mode)) quit(EXIT_FAILURE); break; case 'm': { if (!parse_max_peak_level(optarg, config.max_peak_level)) quit(EXIT_FAILURE); break; } case 't': { config.true_peak = true; break; } case 'l': { if (!parse_target_loudness(optarg, config.target_loudness)) quit(EXIT_FAILURE); break; } case 'O': config.tab_output = OutputType::STDOUT; if (optarg) { const auto& [sep_header, sort_alphanum] = parse_output_mode(optarg); config.sep_header = sep_header; config.sort_alphanum = sort_alphanum; } quiet = 1; break; case 'q': quiet = 1; break; case 'p': config.preserve_mtimes = true; break; case 's': { if (!parse_tag_mode_custom(optarg, config.tag_mode)) quit(EXIT_FAILURE); break; } case 'L': config.lowercase = true; break; case 'I': if (!parse_id3v2_version(optarg, config.id3v2version)) quit(EXIT_FAILURE); break; case 'o': if (!parse_opus_mode(optarg, config.opus_mode)) quit(EXIT_FAILURE); break; case 'h': help_custom(); quit(EXIT_SUCCESS); break; case '?': if (optopt) output_fail("Unrecognized option '{:c}'", optopt); else output_fail("Unrecognized option '{}'", argv[optind - 1] + 2); quit(EXIT_FAILURE); } } nb_files = (unsigned int) (argc - optind); if (!nb_files) { output_fail("No files were specified"); quit(EXIT_FAILURE); } std::unique_ptr job(ScanJob::factory(argv + optind, nb_files, config)); if (!job) { output_fail("File list is not valid"); quit(EXIT_FAILURE); } job->scan(); if (job->error) quit(EXIT_FAILURE); } // Parse main arguments int main(int argc, char *argv[]) { int rc, i; char *command = nullptr; opterr = 0; const char *short_opts = "+hv?"; static struct option long_opts[] = { { "help", no_argument, nullptr, 'h' }, { "version", no_argument, nullptr, 'v' }, { 0, 0, 0, 0 } }; std::locale::global(std::locale("")); av_log_set_callback(nullptr); #ifdef _WIN32 init_console(); #endif while ((rc = getopt_long(argc, argv, short_opts, long_opts, &i)) !=-1) { switch (rc) { case 'h': help_main(); quit(EXIT_SUCCESS); break; case 'v': version(); quit(EXIT_SUCCESS); break; case '?': if (optopt) output_fail("Unrecognized option '{:c}'", optopt); else output_fail("Unrecognized option '{}'", argv[optind - 1] + 2); quit(EXIT_FAILURE); break; } } if (argc == optind) { help_main(); quit(EXIT_SUCCESS); } // Parse and run command command = argv[optind]; char **subargs = argv + optind; int num_subargs = argc - optind; optind = 1; if (MATCH(command, "easy")) easy_mode(num_subargs, subargs); else if (MATCH(command, "custom")) custom_mode(num_subargs, subargs); else { output_fail("Invalid command '{}'", command); quit(EXIT_FAILURE); } quit(EXIT_SUCCESS); } static void help_main() { print(COLOR_RED "Usage: " COLOR_OFF "{}{}{} [OPTIONS] ...\n", COLOR_GREEN, EXECUTABLE_TITLE, COLOR_OFF); print("{} {} supports writing tags to the following file types:\n", PROJECT_NAME, PROJECT_VERSION); print(" FLAC (.flac), Ogg (.ogg, .oga, .spx), Opus (.opus), MP2 (.mp2),\n"); print(" MP3 (.mp3), MP4 (.m4a), WMA (.wma), WavPack (.wv), APE (.ape),\n"); print(" WAV (.wav), AIFF (.aiff, .aif, .snd), and TAK (.tak).\n"); print("\n"); print(COLOR_RED "Options:\n" COLOR_OFF); CMD_HELP("--help", "-h", "Show this help"); CMD_HELP("--version", "-v", "Show version number"); print("\n"); print(COLOR_RED "Commands:\n" COLOR_OFF); CMD_CMD("easy", "Easy Mode: Recursively scan a directory with recommended settings"); CMD_CMD("custom", "Custom Mode: Scan individual files with custom settings"); print("\n"); print("Run '{} easy --help' or '{} custom --help' for more information.", EXECUTABLE_TITLE, EXECUTABLE_TITLE); print("\n\n"); print("Please report any issues to " PROJECT_URL "/issues\n\n"); } static inline void help_custom() { print(COLOR_RED "Usage: " COLOR_OFF "{}{}{} custom [OPTIONS] FILES...\n", COLOR_GREEN, EXECUTABLE_TITLE, COLOR_OFF); print(" Custom Mode allows the user to specify the options to scan the files with. The\n"); print(" list of files to scan must be listed explicitly after the options.\n"); print("\n"); print(COLOR_RED "Options:\n" COLOR_OFF); CMD_HELP("--help", "-h", "Show this help"); print("\n"); CMD_HELP("--album", "-a", "Calculate album gain and peak"); CMD_HELP("--skip-existing", "-S", "Don't scan files with existing ReplayGain information"); print("\n"); CMD_HELP("--tagmode=s", "-s s", "Scan files but don't write ReplayGain tags (default)"); CMD_HELP("--tagmode=d", "-s d", "Delete ReplayGain tags from files"); CMD_HELP("--tagmode=i", "-s i", "Scan and write ReplayGain 2.0 tags to files"); print("\n"); CMD_HELP("--loudness=n", "-l n", "Use n LUFS as target loudness (" STR(MIN_TARGET_LOUDNESS) " ≤ n ≤ " STR(MAX_TARGET_LOUDNESS) ")"); print("\n"); CMD_HELP("--clip-mode=n", "-c n", "No clipping protection (default)"); CMD_HELP("--clip-mode=p", "-c p", "Clipping protection enabled for positive gain values only"); CMD_HELP("--clip-mode=a", "-c a", "Clipping protection always enabled"); CMD_HELP("--max-peak=n", "-m n", "Use max peak level n dB for clipping protection"); CMD_HELP("--true-peak", "-t", "Use true peak for peak calculations"); print("\n"); CMD_HELP("--lowercase", "-L", "Write lowercase tags (MP2/MP3/MP4/WMA/WAV/AIFF)"); CMD_CONT("This is non-standard but sometimes needed"); CMD_HELP("--id3v2-version=keep", "-I keep", "Keep file's existing ID3v2 version, 3 if none exists (default)"); CMD_HELP("--id3v2-version=3", "-I 3", "Write ID3v2.3 tags to MP2/MP3/WAV/AIFF"); CMD_HELP("--id3v2-version=4", "-I 4", "Write ID3v2.4 tags to MP2/MP3/WAV/AIFF"); print("\n"); CMD_HELP("--opus-mode=d", "-o d", "Write standard ReplayGain tags, clear header output gain (default)"); CMD_HELP("--opus-mode=r", "-o r", "Write R128_*_GAIN tags, clear header output gain"); CMD_HELP("--opus-mode=s", "-o s", "Same as 'r', plus override target loudness to -23 LUFS"); CMD_HELP("--opus-mode=t", "-o t", "Write track gain to header output gain"); CMD_HELP("--opus-mode=a", "-o a", "Write album gain to header output gain"); print("\n"); CMD_HELP("--output", "-O", "Output tab-delimited scan data to stdout"); CMD_HELP("--output=s", "-O s", "Output with sep header (needed for Microsoft Excel compatibility)"); CMD_HELP("--output=a", "-O a", "Output with files sorted in alphanumeric order"); CMD_HELP("--preserve-mtimes", "-p", "Preserve file mtimes"); CMD_HELP("--quiet", "-q", "Don't print scanning status messages"); print("\n"); print("Please report any issues to " PROJECT_URL "/issues\n"); print("\n"); } static void version() { unsigned int ffver; int ebur128_v_major = 0; int ebur128_v_minor = 0; int ebur128_v_patch = 0; print(COLOR_GREEN PROJECT_NAME COLOR_OFF " " PROJECT_VERSION #if defined COMMITS_SINCE_TAG && defined COMMIT_HASH "-r" COMMITS_SINCE_TAG "-" COMMIT_HASH #endif " - using:\n" ); // Library versions ebur128_get_version(&ebur128_v_major, &ebur128_v_minor, &ebur128_v_patch); PRINT_LIB("libebur128", format("{}.{}.{}", ebur128_v_major, ebur128_v_minor, ebur128_v_patch)); PRINT_LIB_FFMPEG("libavformat", avformat_version); PRINT_LIB_FFMPEG("libavcodec", avcodec_version); PRINT_LIB_FFMPEG("libavutil", avutil_version); PRINT_LIB_FFMPEG("libswresample", swresample_version); #if HAS_TAGLIB2 TagLib::VersionNumber tver = TagLib::runtimeVersion(); PRINT_LIB("TagLib", format("{}.{}{}", tver.majorVersion(), tver.minorVersion(), tver.patchVersion() ? format(".{}", tver.patchVersion()) : "")); #else print("\n"); print("Built with:\n"); PRINT_LIB("TagLib", format("{}.{}{}", TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION, TAGLIB_PATCH_VERSION ? format(".{}", TAGLIB_PATCH_VERSION) : "")); #endif print("\n"); #if defined(__GNUC__) && !defined(__clang__) print(COLOR_YELLOW "{:<17}" COLOR_OFF " GCC {}.{}\n", "Compiler:", __GNUC__, __GNUC_MINOR__); #endif #ifdef __clang__ print(COLOR_YELLOW "{:<17}" COLOR_OFF " " #ifdef __apple_build_version__ "Apple " #endif "Clang {}.{}.{}\n", "Compiler:", __clang_major__, __clang_minor__, __clang_patchlevel__); #endif #ifdef _MSC_VER print(COLOR_YELLOW "{:<17}" COLOR_OFF " Microsoft C/C++ {:.2f}\n", "Compiler:", (float) _MSC_VER / 100.0f); #endif print(COLOR_YELLOW "{:<17}" COLOR_OFF " " BUILD_DATE "\n", "Build Date:"); } rsgain-3.5.1/src/rsgain.hpp000066400000000000000000000030621463075324200155750ustar00rootroot00000000000000#pragma once #define CMD_HELP(CMDL, CMDS, MSG) print(" {}{:<8} {:<20}{} {}.\n", COLOR_YELLOW, CMDS ",", CMDL, COLOR_OFF, MSG); #define CMD_CMD(CMD, MSG) print(" {}{:<22}{} {}.\n", COLOR_YELLOW, CMD, COLOR_OFF, MSG); #define CMD_CONT(MSG) print(" {}{:<8} {:<20}{} {}.\n", COLOR_YELLOW, "", "", COLOR_OFF, MSG); #define MATCH(x,y) !strcmp(x,y) #define STR_CAT(a) #a #define STR(a) STR_CAT(a) #define MAX_TARGET_LOUDNESS -5 #define MIN_TARGET_LOUDNESS -30 #define RG_TARGET_LOUDNESS -18.0 #define ID3V2_KEEP 0 enum class OutputType{ NONE, STDOUT, FILE, }; struct Config { char tag_mode; bool skip_existing; double target_loudness; double max_peak_level; bool true_peak; char clip_mode; bool do_album; OutputType tab_output; bool sep_header; bool sort_alphanum; bool lowercase; unsigned int id3v2version; char opus_mode; bool skip_mp4; bool preserve_mtimes; }; void quit(int status); bool parse_mode(const char *name, const char *valid_modes, const char *value, char &mode); #define parse_tag_mode_easy(value, mode) parse_mode("tag", "disn", value, mode) #define parse_tag_mode_custom(value, mode) parse_mode("tag", "dis", value, mode) #define parse_clip_mode(value, mode) parse_mode("clip", "npa", value, mode) #define parse_opus_mode(value, mode) parse_mode("Opus", "drtas", value, mode) bool parse_target_loudness(const char *value, double &target_loudness); bool parse_id3v2_version(const char *value, unsigned int &version); bool parse_max_peak_level(const char *value, double &peak); std::pair parse_output_mode(const std::string_view arg); rsgain-3.5.1/src/scan.cpp000066400000000000000000000576641463075324200152520ustar00rootroot00000000000000/* * Loudness normalizer based on the EBU R128 standard * * Copyright (c) 2014, Alessandro Ghedini * All rights reserved. * * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include extern "C" { #include #include #include #include #include #include } #include "rsgain.hpp" #include "easymode.hpp" #include "scan.hpp" #include "output.hpp" #include "tag.hpp" #define output_fferror(e, msg) char errbuf[256]; av_strerror(e, errbuf, sizeof(errbuf)); output_error(msg ": {}", errbuf) #define OLD_CHANNEL_LAYOUT LIBAVUTIL_VERSION_MAJOR < 57 || (LIBAVUTIL_VERSION_MAJOR == 57 && LIBAVUTIL_VERSION_MINOR < 18) #define OUTPUT_FORMAT AV_SAMPLE_FMT_S16 extern bool multithread; // A function to determine a file type static FileType determine_filetype(const std::string &extension) { static const std::unordered_map map = { {".mp2", FileType::MP2}, {".mp3", FileType::MP3}, {".flac", FileType::FLAC}, {".ogg", FileType::OGG}, {".oga", FileType::OGG}, {".spx", FileType::OGG}, {".opus", FileType::OPUS}, {".m4a", FileType::M4A}, {".mp4", FileType::M4A}, {".wma", FileType::WMA}, {".wav", FileType::WAV}, {".aiff", FileType::AIFF}, {".aif", FileType::AIFF}, {".snd", FileType::AIFF}, {".wv", FileType::WAVPACK}, {".ape", FileType::APE}, {".tak", FileType::TAK}, {".mpc", FileType::MPC} }; std::string extensionlower = extension; std::transform(extensionlower.begin(), extensionlower.end(), extensionlower.begin(), ::tolower); auto it = map.find(extensionlower); return it == map.end() ? FileType::INVALID : it->second; } ScanJob* ScanJob::factory(const std::filesystem::path &path) { std::unordered_set extensions; FileType file_type; std::vector tracks; for (const std::filesystem::directory_entry &entry : std::filesystem::directory_iterator(path)) { if (entry.is_regular_file() && entry.path().has_extension() && ((file_type = determine_filetype(entry.path().extension().string())) != FileType::INVALID) && !(file_type == FileType::M4A && get_config(file_type).skip_mp4 && entry.path().extension().string() == ".mp4")) { tracks.emplace_back(entry.path(), file_type); extensions.insert(file_type); } } if (tracks.empty()) return nullptr; file_type = extensions.size() > 1 ? FileType::DEFAULT : *extensions.begin(); const Config &config = get_config(file_type); if (config.tag_mode == 'n') return nullptr; return new ScanJob(path, tracks, config, file_type); } ScanJob* ScanJob::factory(char **files, size_t nb_files, const Config &config) { FileType file_type; std::filesystem::path path; std::vector tracks; std::unordered_set types; for (size_t i = 0; i < nb_files; i++) { path = files[i]; if (!std::filesystem::exists(path)) { output_error("File '{}' does not exist", path.string()); return nullptr; } else if ((file_type = determine_filetype(path.extension().string())) == FileType::INVALID) { output_error("File '{}' is not of a supported type", files[i]); return nullptr; } else { tracks.emplace_back(path, file_type); types.insert(file_type); } } if (tracks.empty()) return nullptr; return new ScanJob(tracks, config, types.size() > 1 ? FileType::DEFAULT : *types.begin()); } void free_ebur128(ebur128_state *ebur128_state) { if (ebur128_state) ebur128_destroy(&ebur128_state); } bool ScanJob::scan(std::mutex *ffmpeg_mutex) { if (config.tag_mode != 'd') { if (config.skip_existing) { std::vector existing; for (auto track = tracks.rbegin(); track != tracks.rend(); ++track) { if (tag_exists(*track)) existing.push_back((int) (tracks.rend() - track - 1)); } size_t nb_exists = existing.size(); if (nb_exists) { if (nb_exists == tracks.size()) { nb_files = 0; skipped = nb_exists; return true; } else if (!config.do_album) { for (int i : existing) { tracks.erase(tracks.begin() + i); skipped++; nb_files--; } } } } for (Track &track : tracks) { error = !track.scan(config, ffmpeg_mutex); if (error) return false; } calculate_loudness(); } tag_tracks(); return true; } bool ScanJob::Track::scan(const Config &config, std::mutex *m) { ProgressBar progress_bar; int rc, stream_id = -1; uint8_t *swr_out_data[1]; bool ret = false; bool repeat = false; int peak_mode; double time_base; bool output_progress = !quiet && !multithread && config.tag_mode != 'd'; std::unique_lock *lk = nullptr; ebur128_state *ebur128 = nullptr; int nb_channels; #if LIBAVCODEC_VERSION_MAJOR >= 59 const #endif AVCodec *codec = nullptr; AVPacket *packet = nullptr; AVCodecContext *codec_ctx = nullptr; AVFrame *frame = nullptr; SwrContext *swr = nullptr; AVFormatContext *format_ctx = nullptr; const AVStream *stream = nullptr; // For Opus files, FFmpeg always adjusts the decoded audio samples by the header output // gain with no way to disable. To get the actual loudness of the audio signal, // we need to set the header output gain to 0 dB before decoding if (type == FileType::OPUS && config.tag_mode != 's') set_opus_header_gain(path.string().c_str(), 0); if (m) lk = new std::unique_lock(*m, std::defer_lock); if (output_progress) output_ok("Scanning '{}'", path.string()); if (lk) lk->lock(); rc = avformat_open_input(&format_ctx, format("file:{}", path.string()).c_str(), nullptr, nullptr); if (rc < 0) { output_fferror(rc, "Could not open input"); goto end; } container = format_ctx->iformat->name; if (output_progress) output_ok("Container: {} [{}]", format_ctx->iformat->long_name, format_ctx->iformat->name); rc = avformat_find_stream_info(format_ctx, nullptr); if (rc < 0) { output_fferror(rc, "Could not find stream info"); goto end; } // Select the best audio stream stream_id = av_find_best_stream(format_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); if (stream_id < 0) { output_error("Could not find audio stream"); goto end; } stream = format_ctx->streams[stream_id]; time_base = av_q2d(stream->time_base); // Initialize the decoder do { codec_ctx = avcodec_alloc_context3(codec); if (!codec_ctx) { output_error("Could not allocate audio codec context"); goto end; } avcodec_parameters_to_context(codec_ctx, stream->codecpar); rc = avcodec_open2(codec_ctx, codec, nullptr); if (rc < 0) { if (!repeat) { #if LIBAVCODEC_VERSION_MAJOR >= 59 const #endif AVCodec *try_codec; avcodec_free_context(&codec_ctx); codec_ctx = nullptr; // For AAC files, try the Fraunhofer decoder if the native FFmpeg decoder failed if (codec->id == AV_CODEC_ID_AAC) { try_codec = avcodec_find_decoder_by_name("libfdk_aac"); if (try_codec) { codec = try_codec; repeat = true; continue; } } } output_fferror(rc, "Could not open codec"); goto end; } repeat = false; } while (repeat); codec_id = codec->id; #if OLD_CHANNEL_LAYOUT nb_channels = codec_ctx->channels; #else nb_channels = codec_ctx->ch_layout.nb_channels; #endif // Display some information about the file if (output_progress) output_ok("Stream #{}: {}, {}{:L} Hz, {} ch", stream_id, codec->long_name, codec_ctx->bits_per_raw_sample > 0 ? format("{} bit, ", codec_ctx->bits_per_raw_sample) : "", codec_ctx->sample_rate, nb_channels ); // Only initialize swresample if we need to convert the format if (codec_ctx->sample_fmt != OUTPUT_FORMAT) { #if OLD_CHANNEL_LAYOUT if (!codec_ctx->channel_layout) codec_ctx->channel_layout = av_get_default_channel_layout(codec_ctx->channels); swr = swr_alloc_set_opts(nullptr, codec_ctx->channel_layout, OUTPUT_FORMAT, codec_ctx->sample_rate, codec_ctx->channel_layout, codec_ctx->sample_fmt, codec_ctx->sample_rate, 0, nullptr ); #else swr_alloc_set_opts2(&swr, &codec_ctx->ch_layout, OUTPUT_FORMAT, codec_ctx->sample_rate, &codec_ctx->ch_layout, codec_ctx->sample_fmt, codec_ctx->sample_rate, 0, nullptr ); #endif if (!swr) { output_error("Could not allocate libswresample context"); goto end; } rc = swr_init(swr); if (rc < 0) { output_fferror(rc, "Could not open libswresample context"); goto end; } } if (lk) lk->unlock(); // Initialize libebur128 peak_mode = config.true_peak ? EBUR128_MODE_TRUE_PEAK : EBUR128_MODE_SAMPLE_PEAK; ebur128 = ebur128_init((unsigned int) nb_channels, (size_t) codec_ctx->sample_rate, EBUR128_MODE_I | peak_mode ); if (!ebur128) { output_error("Could not initialize libebur128 scanner"); goto end; } // Allocate AVPacket structure packet = av_packet_alloc(); if (!packet) { output_error("Could not allocate packet"); goto end; } // Alocate AVFrame structure frame = av_frame_alloc(); if (!frame) { output_error("Could not allocate frame"); goto end; } if (output_progress) { if (stream->duration == AV_NOPTS_VALUE) output_progress = false; else { int start = 0; if (stream->start_time != AV_NOPTS_VALUE) start = (int) std::round((double) stream->start_time * time_base); progress_bar.begin(start, (int) std::round((double) stream->duration * time_base)); } } while (av_read_frame(format_ctx, packet) == 0) { if (packet->stream_index == stream_id) { if ((rc = avcodec_send_packet(codec_ctx, packet)) == 0) { while ((rc = avcodec_receive_frame(codec_ctx, frame)) >= 0) { #if OLD_CHANNEL_LAYOUT if (frame->channels == nb_channels) { #else if (frame->ch_layout.nb_channels == nb_channels) { #endif // Convert audio format with libswresample if necessary if (swr) { size_t out_size = static_cast( av_samples_get_buffer_size(nullptr, nb_channels, frame->nb_samples, OUTPUT_FORMAT, 0 ) ); swr_out_data[0] = (uint8_t*) av_malloc(out_size); if (swr_convert(swr, swr_out_data, frame->nb_samples, (const uint8_t**) frame->data, frame->nb_samples) < 0) { output_error("Could not convert audio frame"); av_free(swr_out_data[0]); goto end; } ebur128_add_frames_short(ebur128, (short*) swr_out_data[0], static_cast(frame->nb_samples)); av_free(swr_out_data[0]); } // Audio is already in correct format else ebur128_add_frames_short(ebur128, (short*) frame->data[0], static_cast(frame->nb_samples)); if (output_progress) { int pos = (int) std::round((double) frame->pts * time_base); if (pos >= 0) progress_bar.update(pos); } } av_frame_unref(frame); } } } av_packet_unref(packet); } // Make sure the progress bar finishes at 100% if (output_progress) progress_bar.complete(); ret = true; end: av_packet_free(&packet); av_frame_free(&frame); if (codec_ctx) avcodec_free_context(&codec_ctx); if (format_ctx) avformat_close_input(&format_ctx); if (swr) swr_free(&swr); // Use a smart pointer to manage the remaining lifetime of the ebur128 state if (ebur128) this->ebur128 = std::unique_ptr(ebur128, free_ebur128); delete lk; return ret; } void ScanJob::calculate_loudness() { if (tracks.empty()) return; // Track loudness calculations for (Track &track : tracks) track.calculate_loudness(config); // Album loudness calculations if (config.do_album) calculate_album_loudness(); // Check clipping conditions if (config.clip_mode != 'n') { double t_new_peak; // Track peak after application of gain double a_new_peak; // Album peak after application of gain double max_peak = pow(10.0, config.max_peak_level / 20.0); // Track clipping for (Track &track : tracks) { if (config.clip_mode == 'a' || (config.clip_mode == 'p' && (track.result.track_gain > 0.0))) { t_new_peak = pow(10.0, track.result.track_gain / 20.0) * track.result.track_peak; if (t_new_peak > max_peak) { double adjustment = 20.0 * log10(t_new_peak / max_peak); if (config.clip_mode == 'p' && adjustment > track.result.track_gain) adjustment = track.result.track_gain; track.result.track_gain -= adjustment; track.tclip = true; } } } // Album clipping double album_gain = tracks[0].result.album_gain; double album_peak = tracks[0].result.album_peak; if (config.do_album && (config.clip_mode == 'a' || (config.clip_mode == 'p' && album_gain > 0.0))) { a_new_peak = pow(10.0, album_gain / 20.0) * album_peak; if (a_new_peak > max_peak) { double adjustment = 20.0 * log10(a_new_peak / max_peak); if (config.clip_mode == 'p' && adjustment > album_gain) adjustment = album_gain; for (Track &track : tracks) { track.result.album_gain -= adjustment; track.aclip = true; } } } } } void ScanJob::tag_tracks() { if (tracks.empty()) return; std::FILE *stream = nullptr; if (config.tab_output != OutputType::NONE) { if (config.tab_output == OutputType::FILE) { std::filesystem::path output_file = path / "replaygain.csv"; stream = fopen(output_file.string().c_str(), "wb"); } else stream = stdout; if (stream) { if (config.sep_header) fputs("sep=\t\n", stream); fputs("Filename\tLoudness (LUFS)\tGain (dB)\tPeak\t Peak (dB)\tPeak Type\tClipping Adjustment?\n", stream); } } // Tag the files bool tab_output = config.tab_output != OutputType::NONE && stream != nullptr; bool human_output = !multithread && !quiet && config.tag_mode != 'd'; if (config.sort_alphanum) std::sort(tracks.begin(), tracks.end(), [](const auto &a, const auto &b){ return a.path.string() < b.path.string(); }); for (Track &track : tracks) { if (config.tag_mode != 's') tag_track(track, config); if (tab_output) { // Filename;Loudness;Gain (dB);Peak;Peak (dB);Peak Type;Clipping Adjustment; print(stream, "{}\t", track.path.filename().string()); track.result.track_loudness == -HUGE_VAL ? print(stream, "-∞\t") : print(stream, "{:.2f}\t", track.result.track_loudness); print(stream, "{:.2f}\t", track.result.track_gain); print(stream, "{:.6f}\t", track.result.track_peak); track.result.track_peak == 0.0 ? print(stream, "-∞\t") : print(stream, "{:.2f}\t", 20.0 * log10(track.result.track_peak)); print(stream, "{}\t", config.true_peak ? "True" : "Sample"); print(stream, "{}\n", track.tclip ? "Y" : "N"); if (config.do_album && ((size_t) (&track - &tracks[0]) == (nb_files - 1))) { print(stream, "{}\t", "Album"); track.result.album_loudness == -HUGE_VAL ? print(stream, "-∞\t") : print(stream, "{:.2f}\t", track.result.album_loudness); print(stream, "{:.2f}\t", track.result.album_gain); print(stream, "{:.6f}\t", track.result.album_peak); track.result.album_peak == 0.0 ? print(stream, "-∞\t") : print(stream, "{:.2f}\t", 20.0 * log10(track.result.album_peak)); print(stream, "{}\t", config.true_peak ? "True" : "Sample"); print(stream, "{}\n", track.aclip ? "Y" : "N"); } } // Human-readable output if (human_output) { print("\nTrack: {}\n", track.path.string()); print(" Loudness: {} LUFS\n", track.result.track_loudness == -HUGE_VAL ? " -∞" : format("{:8.2f}", track.result.track_loudness)); print(" Peak: {:8.6f} ({} dB)\n", track.result.track_peak, track.result.track_peak == 0.0 ? "-∞" : format("{:.2f}", 20.0 * log10(track.result.track_peak)) ); print(" Gain: {:8.2f} dB {}{}\n", track.result.track_gain, track.type == FileType::OPUS && (config.opus_mode == 'r' || config.opus_mode == 's') ? format("({})", GAIN_TO_Q78(track.result.track_gain)) : "", track.tclip ? " (adjusted to prevent clipping)" : "" ); if (config.do_album && ((size_t) (&track - &tracks[0]) == (nb_files - 1))) { print("\nAlbum:\n"); print(" Loudness: {} LUFS\n", track.result.album_loudness == -HUGE_VAL ? " -∞" : format("{:8.2f}", track.result.album_loudness)); print(" Peak: {:8.6f} ({} dB)\n", track.result.album_peak, track.result.album_peak == 0.0 ? "-∞" : format("{:.2f}", 20.0 * log10(track.result.album_peak)) ); print(" Gain: {:8.2f} dB {}{}\n", track.result.album_gain, type == FileType::OPUS && (config.opus_mode == 'r' || config.opus_mode == 's') ? format("({})", GAIN_TO_Q78(track.result.album_gain)) : "", track.aclip ? " (adjusted to prevent clipping)" : "" ); } print("\n"); } } if (config.tab_output == OutputType::FILE && stream != nullptr) fclose(stream); } void ScanJob::update_data(ScanData &data) { if (error) { data.error_directories.push_back(path.string()); return; } data.files += nb_files; data.skipped += skipped; if (!nb_files) return; // Collect clipping stats for (const Track &track : tracks) { if (track.aclip || track.tclip) data.clipping_adjustments++; } if (config.tag_mode != 'd') { for (const Track &track : tracks) { data.total_gain += track.result.track_gain; data.total_peak += track.result.track_peak; track.result.track_gain > 0.0 ? data.total_positive++ : data.total_negative++; } } } void ScanJob::Track::calculate_loudness(const Config &config) { unsigned int channel = 0; double track_loudness, track_peak; if (ebur128_loudness_global(ebur128.get(), &track_loudness) != EBUR128_SUCCESS) track_loudness = config.target_loudness; // Edge case for completely silent tracks if (track_loudness == -HUGE_VAL) { result.track_gain = 0.0; result.track_peak = 0.0; result.track_loudness = -HUGE_VAL; } else { std::vector peaks(ebur128->channels); int (*get_peak)(ebur128_state*, unsigned int, double*) = config.true_peak ? ebur128_true_peak : ebur128_sample_peak; for (double &pk : peaks) get_peak(ebur128.get(), channel++, &pk); track_peak = *std::max_element(peaks.begin(), peaks.end()); result.track_gain = (type == FileType::OPUS && config.opus_mode == 's' ? -23.0 : config.target_loudness) - track_loudness; result.track_peak = track_peak; result.track_loudness = track_loudness; } } void ScanJob::calculate_album_loudness() { double album_loudness, album_peak; size_t nb_states = tracks.size(); std::vector states(nb_states); for (const Track &track : tracks) if (track.result.track_loudness != -HUGE_VAL) states.emplace_back(track.ebur128.get()); if (ebur128_loudness_global_multiple(states.data(), states.size(), &album_loudness) != EBUR128_SUCCESS) album_loudness = config.target_loudness; album_peak = std::max_element(tracks.begin(), tracks.end(), [](const auto &a, const auto &b) { return a.result.track_peak < b.result.track_peak; } )->result.track_peak; double album_gain = (type == FileType::OPUS && config.opus_mode == 's' ? -23.0 : config.target_loudness) - album_loudness; for (Track &track : tracks) { track.result.album_gain = album_gain; track.result.album_peak = album_peak; track.result.album_loudness = album_loudness; } } rsgain-3.5.1/src/scan.hpp000066400000000000000000000041301463075324200152330ustar00rootroot00000000000000#pragma once #include #include #include #include void free_ebur128(ebur128_state *ebur128); enum class FileType { INVALID = -1, DEFAULT, MP2, MP3, FLAC, OGG, OPUS, M4A, WMA, WAV, AIFF, WAVPACK, APE, TAK, MPC }; struct ScanResult { double track_gain; double track_peak; double track_loudness; double album_gain; double album_peak; double album_loudness; }; struct ScanData { size_t files = 0; size_t skipped = 0; size_t clipping_adjustments = 0; double total_gain = 0.0; double total_peak = 0.0; size_t total_negative = 0; size_t total_positive = 0; std::vector error_directories; }; class ScanJob { public: struct Track { std::filesystem::path path; FileType type; std::unique_ptr ebur128; std::string container; ScanResult result; int codec_id; bool tclip = false; bool aclip = false; Track(const std::filesystem::path &path, FileType type) : path(path), type(type), ebur128(nullptr, free_ebur128) {}; bool scan(const Config &config, std::mutex *ffmpeg_mutex); void calculate_loudness(const Config &config); }; std::filesystem::path path; size_t nb_files; const Config &config; FileType type; bool error = false; size_t clipping_adjustments = 0; size_t skipped = 0; ScanJob(const std::filesystem::path &path, std::vector &tracks, const Config &config, FileType &type) : path(path), nb_files(tracks.size()), config(config), type(type), tracks(std::move(tracks)) {} ScanJob(std::vector &tracks, const Config &config, FileType type) : nb_files(tracks.size()), config(config), type(type), tracks(std::move(tracks)) {} static ScanJob* factory(char **files, size_t nb_files, const Config &config); static ScanJob* factory(const std::filesystem::path &path); bool scan(std::mutex *ffmpeg_mutex = nullptr); void update_data(ScanData &data); private: std::vector tracks; void calculate_loudness(); void calculate_album_loudness(); void tag_tracks(); }; rsgain-3.5.1/src/tag.cpp000066400000000000000000000655541463075324200150760ustar00rootroot00000000000000/* * Loudness normalizer based on the EBU R128 standard * * Copyright (c) 2014, Alessandro Ghedini * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CRCPP_USE_CPP11 #include "external/CRC.h" #include "rsgain.hpp" #include "scan.hpp" #include "tag.hpp" #include "output.hpp" #define TAGLIB_VERSION (TAGLIB_MAJOR_VERSION * 10000 + TAGLIB_MINOR_VERSION * 100 + TAGLIB_PATCH_VERSION) #define FORMAT_GAIN(gain) format("{:.2f} dB", gain) #define FORMAT_PEAK(peak) format("{:.6f}", peak) #define OPUS_HEADER_SIZE 47 #define OGG_ROW_SIZE 4 #define OPUS_HEAD_OFFSET 7 * OGG_ROW_SIZE #define OGG_CRC_OFFSET 5 * OGG_ROW_SIZE + 2 #define OGG_SEGMENT_TABLE_OFFSET 27 #define OPUS_GAIN_OFFSET 11 * OGG_ROW_SIZE #define RG_TAGS_UPPERCASE 1 #define RG_TAGS_LOWERCASE 2 #define R128_TAGS 4 #define MP4_ATOM_STRING "----:com.apple.iTunes:" #define FORMAT_MP4_TAG(s, tag) s.append(MP4_ATOM_STRING).append(tag) #define tag_error(t) output_error("Couldn't write to: {}", t.path.string()) using RGTagsArray = std::array; static bool set_mpc_packet_rg(const char *path); static bool tag_mp3(ScanJob::Track &track, const Config &config); static bool tag_flac(ScanJob::Track &track, const Config &config); template static bool tag_ogg(ScanJob::Track &track, const Config &config); static bool tag_mp4(ScanJob::Track &track, const Config &config); template static bool tag_apev2(ScanJob::Track &track, const Config &config); static bool tag_wma(ScanJob::Track &track, const Config &config); template static bool tag_riff(ScanJob::Track &track, const Config &config); template static void write_rg_tags(const ScanResult &result, const Config &config, T&& write_tag); template static void tag_clear_map(T&& clear); static void tag_clear(TagLib::ID3v2::Tag *tag); static void tag_write(TagLib::ID3v2::Tag *tag, const ScanResult &result, const Config &config); template static void tag_clear(TagLib::Ogg::XiphComment *tag); template static void tag_write(TagLib::Ogg::XiphComment *tag, const ScanResult &result, const Config &config); static void tag_clear(TagLib::MP4::Tag *tag); static void tag_write(TagLib::MP4::Tag *tag, const ScanResult &result, const Config &config); static void tag_clear(TagLib::APE::Tag *tag); static void tag_write(TagLib::APE::Tag *tag, const ScanResult &result, const Config &config); static void tag_clear(TagLib::ASF::Tag *tag); static void tag_write(TagLib::ASF::Tag *tag, const ScanResult &result, const Config &config); template static bool tag_exists_id3(const ScanJob::Track &track); template static bool tag_exists_xiph(const ScanJob::Track &track); static bool tag_exists_mp4(const ScanJob::Track &track); template static bool tag_exists_ape(const ScanJob::Track &track); static bool tag_exists_asf(const ScanJob::Track &track); enum class RGTag { TRACK_GAIN, TRACK_PEAK, TRACK_RANGE, ALBUM_GAIN, ALBUM_PEAK, ALBUM_RANGE, REFERENCE_LOUDNESS, MAX_VAL }; static const RGTagsArray RG_STRING_UPPER = {{ "REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_TRACK_PEAK", "REPLAYGAIN_TRACK_RANGE", "REPLAYGAIN_ALBUM_GAIN", "REPLAYGAIN_ALBUM_PEAK", "REPLAYGAIN_ALBUM_RANGE", "REPLAYGAIN_REFERENCE_LOUDNESS" }}; static const RGTagsArray RG_STRING_LOWER = {{ "replaygain_track_gain", "replaygain_track_peak", "replaygain_track_range", "replaygain_album_gain", "replaygain_album_peak", "replaygain_album_range", "replaygain_reference_loudness" }}; static_assert((size_t) RGTag::MAX_VAL == RG_STRING_UPPER.size()); static_assert(RG_STRING_UPPER.size() == RG_STRING_LOWER.size()); enum class R128Tag { TRACK_GAIN, ALBUM_GAIN, MAX_VAL }; static const std::array R128_STRING = {{ "R128_TRACK_GAIN", "R128_ALBUM_GAIN" }}; static_assert((size_t) R128Tag::MAX_VAL == R128_STRING.size()); void tag_track(ScanJob::Track &track, const Config &config) { std::filesystem::file_time_type mtime; if (config.preserve_mtimes) mtime = std::filesystem::last_write_time(track.path); switch (track.type) { case FileType::MP2: case FileType::MP3: if (!tag_mp3(track, config)) tag_error(track); break; case FileType::FLAC: if (!tag_flac(track, config)) tag_error(track); break; case FileType::OGG: switch (track.codec_id) { case AV_CODEC_ID_OPUS: if (!tag_ogg(track, config)) tag_error(track); break; case AV_CODEC_ID_VORBIS: if (!tag_ogg(track, config)) tag_error(track); break; case AV_CODEC_ID_FLAC: if (!tag_ogg(track, config)) tag_error(track); break; case AV_CODEC_ID_SPEEX: if (!tag_ogg(track, config)) tag_error(track); break; default: if (!tag_ogg(track, config)) tag_error(track); break; } break; case FileType::OPUS: if (!tag_ogg(track, config)) tag_error(track); break; case FileType::M4A: if (!tag_mp4(track, config)) tag_error(track); break; case FileType::WMA: if (!tag_wma(track, config)) tag_error(track); break; case FileType::WAV: if (!tag_riff(track, config)) tag_error(track); break; case FileType::AIFF: if (!tag_riff(track, config)) tag_error(track); break; case FileType::WAVPACK: if (!tag_apev2(track, config)) tag_error(track); break; case FileType::APE: case FileType::TAK: if (!tag_apev2(track, config)) tag_error(track); break; case FileType::MPC: if (!tag_apev2(track, config)) tag_error(track); break; default: break; } if (config.preserve_mtimes) std::filesystem::last_write_time(track.path, mtime); } bool tag_exists(const ScanJob::Track &track) { switch(track.type) { case FileType::MP2: case FileType::MP3: return tag_exists_id3(track); case FileType::FLAC: return tag_exists_xiph(track); case FileType::OGG: return tag_exists_xiph(track); case FileType::OPUS: return tag_exists_xiph(track); case FileType::M4A: return tag_exists_mp4(track); case FileType::WMA: return tag_exists_asf(track); case FileType::WAV: return tag_exists_id3(track); case FileType::AIFF: return tag_exists_id3(track); case FileType::WAVPACK: return tag_exists_ape(track); case FileType::APE: case FileType::TAK: return tag_exists_ape(track); case FileType::MPC: return tag_exists_ape(track); default: return false; } return false; } template static bool tag_exists_id3(const ScanJob::Track &track) { const TagLib::ID3v2::Tag *tag = nullptr; T file(track.path.string().c_str(), false); if constexpr (std::is_same_v) tag = file.tag(); else tag = file.ID3v2Tag(); if (tag) { const auto &map = tag->frameListMap(); const auto it = map.find("TXXX"); if (it != map.end()) { const auto &frames = it->second; for (const auto &f : frames) { const auto frame = dynamic_cast(f); if (!frame) continue; if (frame->description().upper() == RG_STRING_UPPER[static_cast(RGTag::TRACK_GAIN)]) return true; } } } return false; } template static bool tag_exists_xiph(const ScanJob::Track &track) { bool ret = false; const TagLib::Ogg::XiphComment *tag = nullptr; T file(track.path.string().c_str(), false); if constexpr(std::is_same_v) tag = file.xiphComment(); else tag = dynamic_cast(file.tag()); if (tag) { ret = tag->contains(RG_STRING_UPPER[static_cast(RGTag::TRACK_GAIN)]); if constexpr(std::is_same_v) { if (!ret) ret = tag->contains(R128_STRING[static_cast(R128Tag::TRACK_GAIN)]); } } return ret; } static bool tag_exists_mp4(const ScanJob::Track &track) { // Build static vector of upper and lowercase RG tags with iTunes atom static std::vector keys; if (keys.empty()) { keys.resize(2); const TagLib::String tags[] = { RG_STRING_UPPER[static_cast(RGTag::TRACK_GAIN)], RG_STRING_LOWER[static_cast(RGTag::TRACK_GAIN)] }; for (auto &key : keys) { key = MP4_ATOM_STRING; key += tags[&key - &keys[0]]; } } TagLib::MP4::File file(track.path.string().c_str(), false); const TagLib::MP4::Tag *tag = file.tag(); if (tag) { for (const auto &key : keys) { if (tag->contains(key)) return true; } } return false; } template static bool tag_exists_ape(const ScanJob::Track &track) { T file(track.path.string().c_str(), false); const TagLib::APE::Tag *tag = file.APETag(); if (tag) { const auto &map = tag->itemListMap(); return map.contains(RG_STRING_UPPER[static_cast(RGTag::TRACK_GAIN)]); } return false; } static bool tag_exists_asf(const ScanJob::Track &track) { TagLib::ASF::File file(track.path.string().c_str(), false); const TagLib::ASF::Tag *tag = file.tag(); return tag->contains(RG_STRING_UPPER[static_cast(RGTag::TRACK_GAIN)]) || tag->contains(RG_STRING_LOWER[static_cast(RGTag::TRACK_GAIN)]); } template static void write_rg_tags(const ScanResult &result, const Config &config, T&& write_tag) { write_tag(RGTag::TRACK_GAIN, FORMAT_GAIN(result.track_gain)); write_tag(RGTag::TRACK_PEAK, FORMAT_PEAK(result.track_peak)); if (config.do_album) { write_tag(RGTag::ALBUM_GAIN, FORMAT_GAIN(result.album_gain)); write_tag(RGTag::ALBUM_PEAK, FORMAT_PEAK(result.album_peak)); } } static bool tag_mp3(ScanJob::Track &track, const Config &config) { TagLib::MPEG::File file(track.path.string().c_str()); TagLib::ID3v2::Tag *tag = file.ID3v2Tag(true); if (!tag) return false; unsigned int id3v2version = config.id3v2version; if (id3v2version == ID3V2_KEEP) id3v2version = tag->isEmpty() ? 3: tag->header()->majorVersion(); tag_clear(tag); if (config.tag_mode == 'i') tag_write(tag, track.result, config); #if TAGLIB_VERSION < 11200 return file.save(TagLib::MPEG::File::ID3v2, false, id3v2version); #else return file.save(TagLib::MPEG::File::ID3v2, TagLib::File::StripTags::StripNone, id3v2version == 3 ? TagLib::ID3v2::Version::v3 : TagLib::ID3v2::Version::v4 ); #endif } static bool tag_flac(ScanJob::Track &track, const Config &config) { TagLib::FLAC::File file(track.path.string().c_str()); TagLib::Ogg::XiphComment *tag = file.xiphComment(true); if (!tag) return false; tag_clear(tag); if (config.tag_mode == 'i') tag_write(tag, track.result, config); return file.save(); } template static bool tag_ogg(ScanJob::Track &track, const Config &config) { T file(track.path.string().c_str()); TagLib::Ogg::XiphComment *tag = nullptr; if constexpr(std::is_same_v) tag = dynamic_cast(file.tag()); else tag = file.tag(); if (!tag) return false; tag_clear(tag); if (config.tag_mode == 'i' && (!std::is_same_v || (config.opus_mode != 't' && config.opus_mode != 'a'))) tag_write(tag, track.result, config); bool ret = file.save(); if (!std::is_same_v || config.tag_mode == 's' || !(config.opus_mode == 't' || config.opus_mode == 'a') || !ret) return ret; int16_t gain = config.opus_mode == 'a' && config.do_album ? GAIN_TO_Q78(track.result.album_gain) : GAIN_TO_Q78(track.result.track_gain); return set_opus_header_gain(track.path.string().c_str(), gain); } static bool tag_mp4(ScanJob::Track &track, const Config &config) { TagLib::MP4::File file(track.path.string().c_str()); TagLib::MP4::Tag *tag = file.tag(); if (!tag) return false; tag_clear(tag); if (config.tag_mode == 'i') tag_write(tag, track.result, config); return file.save(); } template static bool tag_apev2(ScanJob::Track &track, const Config &config) { T file(track.path.string().c_str()); TagLib::APE::Tag *tag = file.APETag(true); if (!tag) return false; tag_clear(tag); if (config.tag_mode == 'i') tag_write(tag, track.result, config); if constexpr(!std::is_same_v) return file.save(); else { bool ret = file.save(); if (ret) ret = set_mpc_packet_rg(track.path.string().c_str()); return ret; } } static bool tag_wma(ScanJob::Track &track, const Config &config) { TagLib::ASF::File file(track.path.string().c_str()); TagLib::ASF::Tag *tag = file.tag(); if (!tag) return false; tag_clear(tag); if (config.tag_mode == 'i') tag_write(tag, track.result, config); return file.save(); } template static bool tag_riff(ScanJob::Track &track, const Config &config) { T file(track.path.string().c_str()); TagLib::ID3v2::Tag *tag = nullptr; if constexpr (std::is_same_v) tag = file.ID3v2Tag(); else if constexpr (std::is_same_v) tag = file.tag(); if (!tag) return false; unsigned int id3v2version = config.id3v2version; if (id3v2version == ID3V2_KEEP) id3v2version = tag->isEmpty() ? 3: tag->header()->majorVersion(); tag_clear(tag); if (config.tag_mode == 'i') tag_write(tag, track.result, config); if constexpr (std::is_same_v) #if TAGLIB_VERSION < 11200 return file.save(T::AllTags, false, id3v2version); #else return file.save(T::AllTags, TagLib::File::StripTags::StripNone, id3v2version == 3 ? TagLib::ID3v2::Version::v3 : TagLib::ID3v2::Version::v4 ); #endif else if constexpr (std::is_same_v) return file.save(); } template static void tag_clear_map(T&& clear) { if constexpr((flags) & RG_TAGS_UPPERCASE) { for (const auto &tag : RG_STRING_UPPER) clear(tag); } if constexpr((flags) & RG_TAGS_LOWERCASE) { for (const auto &tag : RG_STRING_LOWER) clear(tag); } if constexpr((flags) & R128_TAGS) { for (const auto &tag : R128_STRING) clear(tag); } } static void tag_clear(TagLib::ID3v2::Tag *tag) { const auto &map = tag->frameListMap(); const auto it = map.find("TXXX"); if (it == map.end()) return; TagLib::ID3v2::FrameList txxx_frames = it->second; for (auto &f : txxx_frames) { auto frame = dynamic_cast(f); if (frame && frame->fieldList().size() >= 2) { TagLib::String desc = frame->description().upper(); auto rg_tag = std::find_if(RG_STRING_UPPER.begin(), RG_STRING_UPPER.end(), [&](const auto &tag_type) { return desc == tag_type; } ); if (rg_tag != RG_STRING_UPPER.end()) tag->removeFrame(frame); } } // Also remove legacy RVAD (ID3v2.3) and RVA2 (ID3v2.4) which conflict with ReplayGain static const TagLib::ByteVector legacy_frames[] = {"RVAD", "RVA2"}; for (const auto &frame_id : legacy_frames) { const auto it2 = map.find(frame_id); if (it2 != map.end()) { TagLib::ID3v2::FrameList frames = it2->second; for (auto frame : frames) tag->removeFrame(frame); } } } static void tag_write(TagLib::ID3v2::Tag *tag, const ScanResult &result, const Config &config) { const RGTagsArray &RG_STRING = config.lowercase ? RG_STRING_LOWER : RG_STRING_UPPER; write_rg_tags(result, config, [&](RGTag rg_tag, const TagLib::String &value) { auto frame = new TagLib::ID3v2::UserTextIdentificationFrame(); frame->setDescription(RG_STRING[static_cast(rg_tag)]); frame->setText(value); tag->addFrame(frame); } ); } template static void tag_clear(TagLib::Ogg::XiphComment *tag) { if constexpr(std::is_same_v) { tag_clear_map( [&](const TagLib::String &t) { tag->removeFields(t); } ); } else { tag_clear_map( [&](const TagLib::String &t) { tag->removeFields(t); } ); } } template static void tag_write(TagLib::Ogg::XiphComment *tag, const ScanResult &result, const Config &config) { const RGTagsArray &RG_STRING = RG_STRING_UPPER; // Opus RFC 7845 tag if (std::is_same_v && (config.opus_mode == 'r' || config.opus_mode == 's')) { tag->addField(R128_STRING[static_cast(R128Tag::TRACK_GAIN)], format("{}", GAIN_TO_Q78(result.track_gain)) ); if (config.do_album) { tag->addField(R128_STRING[static_cast(R128Tag::ALBUM_GAIN)], format("{}", GAIN_TO_Q78(result.album_gain)) ); } } // Default ReplayGain tag else { write_rg_tags(result, config, [&](RGTag rg_tag, const TagLib::String &value) { tag->addField(RG_STRING[static_cast(rg_tag)], value); } ); } } static void tag_clear(TagLib::MP4::Tag *tag) { tag_clear_map( [&](const TagLib::String &t) { TagLib::String tag_name; FORMAT_MP4_TAG(tag_name, t); tag->removeItem(tag_name); } ); } static void tag_write(TagLib::MP4::Tag *tag, const ScanResult &result, const Config &config) { const RGTagsArray &RG_STRING = config.lowercase ? RG_STRING_LOWER : RG_STRING_UPPER; write_rg_tags(result, config, [&](RGTag rg_tag, const TagLib::String &value) { TagLib::String tag_name; FORMAT_MP4_TAG(tag_name, RG_STRING[static_cast(rg_tag)]); tag->setItem(tag_name, TagLib::MP4::Item(value)); } ); } static void tag_clear(TagLib::APE::Tag *tag) { tag_clear_map( [&](const TagLib::String &t) { tag->removeItem(t); } ); } static void tag_write(TagLib::APE::Tag *tag, const ScanResult &result, const Config &config) { const RGTagsArray &RG_STRING = RG_STRING_UPPER; write_rg_tags(result, config, [&](RGTag rg_tag, const TagLib::String &value) { tag->addValue(RG_STRING[static_cast(rg_tag)], value); } ); } static void tag_clear(TagLib::ASF::Tag *tag) { tag_clear_map( [&](const TagLib::String &t) { tag->removeItem(t); } ); } static void tag_write(TagLib::ASF::Tag *tag, const ScanResult &result, const Config &config) { const RGTagsArray &RG_STRING = config.lowercase ? RG_STRING_LOWER : RG_STRING_UPPER; write_rg_tags(result, config, [&](RGTag rg_tag, const TagLib::String &value) { tag->setAttribute(RG_STRING[static_cast(rg_tag)], value); } ); } static_assert(-1 == ~0); // 2's complement for signed integers bool set_opus_header_gain(const char *path, int16_t gain) { uint32_t crc; if constexpr(std::endian::native == std::endian::big) gain = static_cast((gain << 8) & 0xff00) | ((gain >> 8) & 0x00ff); std::unique_ptr file(fopen(path, "rb+"), fclose); char buffer[8]; size_t page_size = 0; size_t opus_header_size = 0; // Check for OggS header if (fseek(file.get(), 0, SEEK_SET) || fread(buffer, 1, 4, file.get()) != 4 || strncmp(buffer, "OggS", 4)) return false; // Check for OpusHead header if (fseek(file.get(), OPUS_HEAD_OFFSET, SEEK_SET) || fread(buffer, 1, 8, file.get()) != 8 || strncmp(buffer, "OpusHead", 8)) return false; // Read the size of the Opus header if (fseek(file.get(), OGG_SEGMENT_TABLE_OFFSET, SEEK_SET) || fread((void*) &opus_header_size, 1, 1, file.get()) != 1) return false; page_size = OPUS_HEAD_OFFSET + opus_header_size; // To verify the page size, make sure the next Ogg page is where we expect it if (fseek(file.get(), page_size, SEEK_SET) || fread(buffer, 1, 4, file.get()) != 4 || strncmp(buffer, "OggS", 4)) return false; // Read the entire Ogg page into memory auto page = std::make_unique(page_size); if (fseek(file.get(), 0, SEEK_SET) || fread(page.get(), 1, page_size, file.get()) != page_size) return false; // Clear CRC, set gain memset(page.get() + OGG_CRC_OFFSET, 0, sizeof(crc)); memcpy(page.get() + OPUS_GAIN_OFFSET, &gain, sizeof(gain)); // Calculate new CRC static const CRC::Table table({0x04C11DB7, 0, 0, false, false}); crc = CRC::Calculate(page.get(), page_size, table); // Write new CRC and gain to file fseek(file.get(), OGG_CRC_OFFSET, SEEK_SET); fwrite(&crc, sizeof(crc), 1, file.get()); fseek(file.get(), OPUS_GAIN_OFFSET, SEEK_SET); fwrite(&gain, sizeof(gain), 1, file.get()); return true; } static bool set_mpc_packet_rg(const char *path) { std::FILE *fp = fopen(path, "rb+"); if (fp == nullptr) return false; std::unique_ptr file(fp, fclose); fseek(fp, 0L, SEEK_END); size_t nb_bytes = static_cast(ftell(fp)); rewind(fp); // Validate magic number char magic_num[4]; if (fread(magic_num, 1, sizeof(magic_num), fp) != sizeof(magic_num) || strncmp(magic_num, "MPCK", sizeof(magic_num))) return false; nb_bytes -= sizeof(magic_num); // Loop through all the packets until we find "RG" char key[2]; unsigned char length_buffer[4]; unsigned int length_bytes; // Tracks width of length buffer (1-4) uint32_t length; size_t total_bytes_read = 0; size_t payload_bytes; while (total_bytes_read < nb_bytes) { total_bytes_read += fread(key, 1, sizeof(key), fp); // Find length of the packet length_bytes = 0; length = 0; do { total_bytes_read += fread(length_buffer + length_bytes, 1, 1, fp); length_bytes++; } while ((length_buffer[length_bytes - 1] & 0x80) && total_bytes_read < nb_bytes && length_bytes < 4); for (size_t i = 0; i < length_bytes; i++) length += (uint32_t) (0x7F & length_buffer[i]) << (7 * (length_bytes - i - 1)); payload_bytes = length - (2 + length_bytes); // Clear the ReplayGain info if (!strncmp(key, "RG", 2) && length == 12) { static char rg_buffer[] = { 0x1, // version 0x0, 0x0, // track gain 0x0, 0x0, // track peak 0x0, 0x0, // album gain 0x0, 0x0, // album peak }; fwrite(rg_buffer, 1, sizeof(rg_buffer), fp); return true; } total_bytes_read += payload_bytes; fseek(fp, static_cast(payload_bytes), SEEK_CUR); } return false; } rsgain-3.5.1/src/tag.hpp000066400000000000000000000033051463075324200150650ustar00rootroot00000000000000#pragma once /* * Loudness normalizer based on the EBU R128 standard * * Copyright (c) 2014, Alessandro Ghedini * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "scan.hpp" #define GAIN_TO_Q78(gain) static_cast(std::round(gain * 256.0)) void tag_track(ScanJob::Track &track, const Config &config); bool tag_exists(const ScanJob::Track &track); bool set_opus_header_gain(const char *path, int16_t gain); rsgain-3.5.1/vcpkg.json000066400000000000000000000013501463075324200150150ustar00rootroot00000000000000{ "dependencies": [ "taglib", { "name": "getopt", "platform": "windows" } ], "features": { "fmt": { "description": "Use the fmtlib library for formatting", "dependencies": ["fmt"] }, "ffmpeg": { "description": "Build FFmpeg", "dependencies": [ { "name": "ffmpeg", "default-features": false, "features": ["avcodec", "avformat", "swresample", "fdk-aac"] }, { "name": "fdk-aac", "features": ["he-aac"] } ] }, "libebur128": { "description": "Build libebur128", "dependencies": ["libebur128"] }, "inih": { "description": "Build inih", "dependencies": ["inih"] } } }