forked from mia/Aegisub
Compare commits
127 commits
mia
...
try-fix-ma
Author | SHA1 | Date | |
---|---|---|---|
|
40d31a57c9 | ||
|
cd7ee8d505 | ||
|
cb77c0b395 | ||
|
80ef2fbf35 | ||
|
20fc9043e8 | ||
|
a520f8a4a3 | ||
|
1bda6052b6 | ||
|
557e81be1d | ||
|
f2676ddc3a | ||
|
a2fc4bf479 | ||
|
71894fd769 | ||
|
56434c5f58 | ||
|
45cd713ef9 | ||
|
29fd12258f | ||
|
b947116937 | ||
|
309996aeb2 | ||
|
2929b9db37 | ||
|
2ca9cf0de0 | ||
|
0336779735 | ||
|
b86238456f | ||
|
620033915a | ||
|
c1a4e0674b | ||
|
24d52bb1ee | ||
|
7c5cac0316 | ||
|
5fc01de1e5 | ||
|
bd7a7ac551 | ||
|
41c0e49813 | ||
|
f74b3acd0e | ||
|
41bb13cff2 | ||
|
f92abc863e | ||
|
b2be79366e | ||
|
ea4010dcbe | ||
|
8db6c60bc3 | ||
|
0e29c8d0e4 | ||
|
0fbcaea871 | ||
|
7c76136726 | ||
|
d708f3ecd8 | ||
|
22063d7e6b | ||
|
3c2414c0df | ||
|
9c95e81784 | ||
|
716549f2c9 | ||
|
d5eba08cbe | ||
|
af5060d6a3 | ||
|
e51b93683d | ||
|
f1ef3d2d2c | ||
|
a4d49c66d0 | ||
|
7a8e2ec816 | ||
|
186c98308e | ||
|
7c500a096a | ||
|
bd4c7789cf | ||
|
3eff75d56e | ||
|
da699f124e | ||
|
1204a3be85 | ||
|
9e6b7e94c0 | ||
|
ad02d39f44 | ||
|
d914ad72b5 | ||
|
055aa379e5 | ||
|
97792e15a8 | ||
|
4a874de442 | ||
|
5d14137710 | ||
|
d252dcf32d | ||
|
4f351b8b4a | ||
|
c4e0f40370 | ||
|
05c0ba0e46 | ||
|
e4e04c9e87 | ||
|
81160b2ec0 | ||
|
bf28e7efc3 | ||
|
c2c44f1ad2 | ||
|
5dd201bc2d | ||
|
3663d118b6 | ||
|
60a722db31 | ||
|
d65643ddee | ||
|
fbca222295 | ||
|
3225ae39f4 | ||
|
888be0607f | ||
|
4200b85fb4 | ||
|
0b8b286767 | ||
|
139132a964 | ||
|
4a3689d6e7 | ||
|
248e69a9b6 | ||
|
46474e0319 | ||
|
50544cf749 | ||
|
e9a68f22b9 | ||
|
d6ddea0f65 | ||
|
2bbed6c5a0 | ||
|
592250eeaa | ||
|
8f40ca44ce | ||
|
f776db2d2b | ||
|
f9ffc46bf6 | ||
|
019e68147e | ||
|
f733297499 | ||
|
4c6d370d51 | ||
|
b7c640d061 | ||
|
2c06f03f5b | ||
|
1819fc8d8b | ||
|
ba54e8d12f | ||
|
c76e410d30 | ||
|
bb2cfb1fcd | ||
|
9abcc03202 | ||
|
837d5a41d7 | ||
|
875456c803 | ||
|
9ddecfdd46 | ||
|
70f27eae4b | ||
|
8d2ef3fca7 | ||
|
48869ae0ad | ||
|
b00285cf71 | ||
|
c6c9e05406 | ||
|
fc1a78aeca | ||
|
38bb1790ab | ||
|
fff08c4650 | ||
|
163d57d6a2 | ||
|
6266867586 | ||
|
4431f678ce | ||
|
419386aadd | ||
|
c4cce28766 | ||
|
1ae2f60b9a | ||
|
b6eebcd7ef | ||
|
bf55264e6d | ||
|
34575a9786 | ||
|
69310d40ae | ||
|
657d9d5149 | ||
|
043a45cf91 | ||
|
ad15c53fb1 | ||
|
841a35a6fd | ||
|
ee7dc6af4e | ||
|
77da2436c5 | ||
|
7be2325629 |
174 changed files with 10568 additions and 11879 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -5,6 +5,7 @@
|
|||
*.bz2
|
||||
*.cache
|
||||
*.dep
|
||||
*.db
|
||||
*.dll
|
||||
*.dmg
|
||||
*.exe
|
||||
|
@ -14,13 +15,13 @@
|
|||
*.ilk
|
||||
*.log
|
||||
*.manifest
|
||||
!src/dpi_aware.manifest
|
||||
*.mkv
|
||||
*.mo
|
||||
*.ncb
|
||||
*.obj
|
||||
*.opensdf
|
||||
*.orig
|
||||
*.patch
|
||||
*.pch
|
||||
*.pdb
|
||||
*.profdata
|
||||
|
@ -63,6 +64,8 @@ git_version.h
|
|||
git_version.xml
|
||||
packages/desktop/aegisub.desktop
|
||||
packages/desktop/aegisub.desktop.template
|
||||
packages/desktop/aegisub.appdata.xml
|
||||
packages/desktop/aegisub.appdata.xml.template
|
||||
src/aegisub
|
||||
src/libresrc/bitmap.cpp
|
||||
src/libresrc/bitmap.h
|
||||
|
@ -86,3 +89,4 @@ vendor/luajit/src/lj_vm.s
|
|||
vendor/luajit/src/luajit
|
||||
|
||||
.nuget
|
||||
.vs
|
||||
|
|
92
.travis.yml
92
.travis.yml
|
@ -1,65 +1,75 @@
|
|||
sudo: required
|
||||
dist: trusty
|
||||
dist: bionic
|
||||
language: cpp
|
||||
|
||||
git:
|
||||
submodules: false
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode11.3
|
||||
env: BUILD_SUIT=autotools
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- sourceline: 'ppa:ubuntu-toolchain-r/test'
|
||||
- sourceline: 'deb https://apt.kitware.com/ubuntu/ bionic main'
|
||||
key_url: 'https://apt.kitware.com/keys/kitware-archive-latest.asc'
|
||||
packages:
|
||||
- libasound2-dev
|
||||
- libfftw3-dev
|
||||
- libhunspell-dev
|
||||
- yasm
|
||||
- libfribidi-dev
|
||||
- libass-dev
|
||||
- libicu-dev
|
||||
- luarocks
|
||||
- g++-5
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- compiler: gcc
|
||||
env: BOOST_VERSION=55
|
||||
- compiler: gcc
|
||||
env: BOOST_VERSION=60
|
||||
before_install:
|
||||
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 60 --slave /usr/bin/g++ g++ /usr/bin/g++-5 --slave /usr/bin/gcov gcov /usr/bin/gcov-5
|
||||
- cmake
|
||||
- build-essential
|
||||
- libboost-all-dev
|
||||
- libffms2-dev
|
||||
- libfontconfig1-dev
|
||||
- libopenal-dev
|
||||
- libuchardet-dev
|
||||
- libwxgtk3.0-dev
|
||||
- portaudio19-dev
|
||||
- libpulse-dev
|
||||
- autopoint
|
||||
- libgtest-dev
|
||||
- gcc-9
|
||||
- g++-9
|
||||
|
||||
install:
|
||||
# Can't install these via the apt addon due to the whitelist
|
||||
- sudo apt-get install -y -qq libffms2-dev libwxgtk3.0-dev libuchardet-dev
|
||||
|
||||
- sudo pip install cpp-coveralls
|
||||
- sudo luarocks install busted > /dev/null
|
||||
- sudo luarocks install moonscript > /dev/null
|
||||
- sudo luarocks install uuid > /dev/null
|
||||
|
||||
- git submodule --quiet init
|
||||
- git submodule --quiet update vendor/googletest
|
||||
|
||||
- cd vendor
|
||||
- rm -rf boost
|
||||
- wget http://sourceforge.net/projects/boost/files/boost/1.${BOOST_VERSION}.0/boost_1_${BOOST_VERSION}_0.tar.bz2/download
|
||||
- tar xjf download
|
||||
- mv boost_1_${BOOST_VERSION}_0 boost
|
||||
- cd boost
|
||||
- ./bootstrap.sh
|
||||
- ./b2 -j3 -layout=system threading=multi cxxflags=-std=c++11 link=shared variant=release --without-python --without-iostreams --without-serialization --without-graph --without-log --without-math --without-signals --without-test --without-wave --without-mpi --without-program_options --without-graph_parallel --without-context --without-coroutine --without-random --without-timer --without-date_time
|
||||
- cd ../..
|
||||
- ./.travis/install.sh
|
||||
|
||||
script:
|
||||
- export CPATH=$(pwd)/vendor/boost
|
||||
- export LD_LIBRARY_PATH=$(pwd)/vendor/boost/stage/lib:$LD_LIBRARY_PATH
|
||||
- export CPPFLAGS="-fprofile-arcs -ftest-coverage"
|
||||
- export LIBS="-lgcov"
|
||||
- autoreconf -if
|
||||
- ./configure BOOST_LDFLAGS="-L$(pwd)/vendor/boost/stage/lib" --enable-debug || cat config.log
|
||||
- make -j3 all test
|
||||
- coveralls --exclude vendor --exclude src --exclude build --exclude tools --exclude libaegisub/windows > /dev/null
|
||||
- if [ $TRAVIS_OS_NAME = 'osx' ]; then
|
||||
export PATH="/usr/local/opt/gettext/bin:/usr/local/opt/icu4c/sbin:/usr/local/opt/icu4c/bin:$PATH";
|
||||
export CPPFLAGS="-I/usr/local/opt/gettext/include -I/usr/local/opt/icu4c/include";
|
||||
export LDFLAGS="-L/usr/local/opt/gettext/lib -L/usr/local/opt/icu4c/lib";
|
||||
export ACLOCAL_PATH="/usr/local/opt/gettext/share/aclocal"
|
||||
export PKG_CONFIG_PATH="/usr/local/opt/icu4c/lib/pkgconfig";
|
||||
./autogen.sh;
|
||||
./configure --enable-debug || cat config.log;
|
||||
make -j2 || travis_terminate 1;
|
||||
make test || travis_terminate 1;
|
||||
elif [ "$BUILD_SUIT" = "autotools" ]; then
|
||||
export CPPFLAGS="-fprofile-arcs -ftest-coverage";
|
||||
export LIBS="-lgcov";
|
||||
./autogen.sh;
|
||||
./configure --enable-debug || cat config.log;
|
||||
make -j2 || travis_terminate 1;
|
||||
make test || travis_terminate 1;
|
||||
coveralls --exclude vendor --exclude src --exclude build --exclude tools --exclude libaegisub/windows > /dev/null;
|
||||
else
|
||||
./build/version.sh .;
|
||||
mkdir build-dir;
|
||||
cd build-dir;
|
||||
cmake -DCMAKE_CXX_FLAGS='-Wall -Wextra -Wno-unused-parameter -pedantic' -DCMAKE_C_FLAGS='-Wall' -DWITH_STARTUPLOG=ON -DWITH_TEST=ON ..;
|
||||
make -j2 || travis_terminate 1;
|
||||
make test || travis_terminate 1;
|
||||
fi
|
||||
|
||||
notifications:
|
||||
email:
|
||||
|
|
23
.travis/install.sh
Executable file
23
.travis/install.sh
Executable file
|
@ -0,0 +1,23 @@
|
|||
#! /bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ $TRAVIS_OS_NAME = 'osx' ]; then
|
||||
brew install autoconf ffmpeg freetype gettext ffms2 fftw fribidi libass m4 icu4c boost wxmac lua
|
||||
else
|
||||
sudo luarocks install busted > /dev/null
|
||||
sudo luarocks install moonscript > /dev/null
|
||||
sudo luarocks install uuid > /dev/null
|
||||
# Remove the CMake provided by travis
|
||||
sudo rm -rf /usr/local/cmake*
|
||||
if [ "$BUILD_SUIT" = "autotools" ]; then
|
||||
sudo pip install -U cpp-coveralls;
|
||||
git submodule --quiet init;
|
||||
git submodule --quiet update vendor/googletest;
|
||||
else
|
||||
pushd /usr/src/googletest;
|
||||
sudo cmake .;
|
||||
sudo make install -j2;
|
||||
popd;
|
||||
fi
|
||||
fi
|
78
CMakeLists.test.txt
Normal file
78
CMakeLists.test.txt
Normal file
|
@ -0,0 +1,78 @@
|
|||
if(UNIX)
|
||||
add_executable(aegisub-lua EXCLUDE_FROM_ALL
|
||||
automation/tests/aegisub.cpp
|
||||
)
|
||||
target_include_directories(aegisub-lua PRIVATE ${Boost_INCLUDE_DIRS})
|
||||
target_link_directories(aegisub-lua PRIVATE ${Boost_LIBRARY_DIRS})
|
||||
target_link_libraries(aegisub-lua libaegisub luabins luajit ${Boost_LIBRARIES} ${ICU_LIBRARIES})
|
||||
add_custom_target(test-automation
|
||||
COMMAND sh -c "$(luarocks path); ${PROJECT_BINARY_DIR}/aegisub-lua tests/busted.lua -p moon tests/modules"
|
||||
VERBATIM
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/automation"
|
||||
)
|
||||
add_dependencies(test-automation aegisub-lua)
|
||||
else()
|
||||
add_custom_target(test-automation)
|
||||
endif()
|
||||
|
||||
find_package(GTest)
|
||||
if(GTEST_FOUND)
|
||||
add_executable(gtest-run EXCLUDE_FROM_ALL
|
||||
tests/tests/access.cpp
|
||||
tests/tests/audio.cpp
|
||||
tests/tests/cajun.cpp
|
||||
tests/tests/calltip_provider.cpp
|
||||
tests/tests/character_count.cpp
|
||||
tests/tests/color.cpp
|
||||
tests/tests/dialogue_lexer.cpp
|
||||
tests/tests/format.cpp
|
||||
tests/tests/fs.cpp
|
||||
tests/tests/hotkey.cpp
|
||||
tests/tests/iconv.cpp
|
||||
tests/tests/ifind.cpp
|
||||
tests/tests/karaoke_matcher.cpp
|
||||
tests/tests/keyframe.cpp
|
||||
tests/tests/line_iterator.cpp
|
||||
tests/tests/line_wrap.cpp
|
||||
tests/tests/mru.cpp
|
||||
tests/tests/option.cpp
|
||||
tests/tests/path.cpp
|
||||
tests/tests/signals.cpp
|
||||
tests/tests/split.cpp
|
||||
tests/tests/syntax_highlight.cpp
|
||||
tests/tests/thesaurus.cpp
|
||||
tests/tests/time.cpp
|
||||
tests/tests/type_name.cpp
|
||||
tests/tests/util.cpp
|
||||
tests/tests/uuencode.cpp
|
||||
tests/tests/vfr.cpp
|
||||
tests/tests/word_split.cpp
|
||||
tests/support/main.cpp
|
||||
tests/support/util.cpp
|
||||
)
|
||||
target_compile_definitions(gtest-run PRIVATE CMAKE_BUILD)
|
||||
target_include_directories(gtest-run PRIVATE tests/support ${Boost_INCLUDE_DIRS} ${Iconv_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS})
|
||||
target_link_libraries(gtest-run libaegisub ${Boost_LIBRARIES} ${ICU_LIBRARIES} ${GTEST_LIBRARIES})
|
||||
if(MSVC)
|
||||
set_target_properties(gtest-run PROPERTIES COMPILE_FLAGS "/Yu${PROJECT_SOURCE_DIR}/tests/support/tests_pre.h" COMPILE_FLAGS "/FI${PROJECT_SOURCE_DIR}/tests/support/tests_pre.h")
|
||||
else()
|
||||
target_compile_options(gtest-run PRIVATE -include "${PROJECT_SOURCE_DIR}/tests/support/tests_pre.h")
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_custom_target(test-aegisub
|
||||
COMMAND "${PROJECT_SOURCE_DIR}/tests/setup.bat"
|
||||
COMMAND gtest-run
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tests"
|
||||
)
|
||||
else()
|
||||
add_custom_target(test-aegisub
|
||||
COMMAND "${PROJECT_SOURCE_DIR}/tests/setup.sh"
|
||||
COMMAND gtest-run
|
||||
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tests"
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
add_custom_target(test-aegisub)
|
||||
endif()
|
||||
|
||||
add_custom_target(test DEPENDS test-automation test-aegisub)
|
812
CMakeLists.txt
Normal file
812
CMakeLists.txt
Normal file
|
@ -0,0 +1,812 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
|
||||
project(Aegisub)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
||||
|
||||
include_directories("build")
|
||||
include_directories("libaegisub/include")
|
||||
include_directories("vendor/luajit/include")
|
||||
|
||||
add_library(libaegisub STATIC
|
||||
libaegisub/common/parser.cpp
|
||||
libaegisub/ass/dialogue_parser.cpp
|
||||
libaegisub/ass/time.cpp
|
||||
libaegisub/ass/uuencode.cpp
|
||||
libaegisub/audio/provider.cpp
|
||||
libaegisub/audio/provider_convert.cpp
|
||||
libaegisub/audio/provider_dummy.cpp
|
||||
libaegisub/audio/provider_hd.cpp
|
||||
libaegisub/audio/provider_lock.cpp
|
||||
libaegisub/audio/provider_pcm.cpp
|
||||
libaegisub/audio/provider_ram.cpp
|
||||
libaegisub/common/cajun/elements.cpp
|
||||
libaegisub/common/cajun/reader.cpp
|
||||
libaegisub/common/cajun/writer.cpp
|
||||
libaegisub/lua/modules/lfs.cpp
|
||||
libaegisub/lua/modules/re.cpp
|
||||
libaegisub/lua/modules/unicode.cpp
|
||||
libaegisub/lua/modules/lpeg.c
|
||||
libaegisub/lua/modules.cpp
|
||||
libaegisub/lua/script_reader.cpp
|
||||
libaegisub/lua/utils.cpp
|
||||
libaegisub/common/calltip_provider.cpp
|
||||
libaegisub/common/character_count.cpp
|
||||
libaegisub/common/charset.cpp
|
||||
libaegisub/common/charset_6937.cpp
|
||||
libaegisub/common/charset_conv.cpp
|
||||
libaegisub/common/color.cpp
|
||||
libaegisub/common/file_mapping.cpp
|
||||
libaegisub/common/format.cpp
|
||||
libaegisub/common/fs.cpp
|
||||
libaegisub/common/hotkey.cpp
|
||||
libaegisub/common/io.cpp
|
||||
libaegisub/common/json.cpp
|
||||
libaegisub/common/kana_table.cpp
|
||||
libaegisub/common/karaoke_matcher.cpp
|
||||
libaegisub/common/keyframe.cpp
|
||||
libaegisub/common/line_iterator.cpp
|
||||
libaegisub/common/log.cpp
|
||||
libaegisub/common/mru.cpp
|
||||
libaegisub/common/option.cpp
|
||||
libaegisub/common/option_value.cpp
|
||||
libaegisub/common/path.cpp
|
||||
libaegisub/common/thesaurus.cpp
|
||||
libaegisub/common/util.cpp
|
||||
libaegisub/common/vfr.cpp
|
||||
libaegisub/common/ycbcr_conv.cpp
|
||||
libaegisub/common/dispatch.cpp
|
||||
)
|
||||
if(UNIX)
|
||||
target_sources(libaegisub PRIVATE
|
||||
libaegisub/unix/access.cpp
|
||||
libaegisub/unix/fs.cpp
|
||||
libaegisub/unix/log.cpp
|
||||
libaegisub/unix/path.cpp
|
||||
libaegisub/unix/util.cpp
|
||||
)
|
||||
set_property(
|
||||
SOURCE libaegisub/unix/path.cpp
|
||||
PROPERTY COMPILE_DEFINITIONS
|
||||
P_DATA="${CMAKE_INSTALL_PREFIX}/share/aegisub/"
|
||||
)
|
||||
elseif(WIN32)
|
||||
target_sources(libaegisub PRIVATE
|
||||
libaegisub/windows/access.cpp
|
||||
libaegisub/windows/charset_conv_win.cpp
|
||||
libaegisub/windows/fs.cpp
|
||||
libaegisub/windows/lagi_pre.cpp
|
||||
libaegisub/windows/log_win.cpp
|
||||
libaegisub/windows/path_win.cpp
|
||||
libaegisub/windows/util_win.cpp
|
||||
)
|
||||
endif()
|
||||
set_target_properties(libaegisub PROPERTIES PREFIX "")
|
||||
target_compile_definitions(libaegisub PRIVATE CMAKE_BUILD)
|
||||
|
||||
add_library(luabins STATIC
|
||||
vendor/luabins/src/fwrite.c
|
||||
vendor/luabins/src/load.c
|
||||
vendor/luabins/src/luabins.c
|
||||
vendor/luabins/src/luainternals.c
|
||||
vendor/luabins/src/save.c
|
||||
vendor/luabins/src/savebuffer.c
|
||||
vendor/luabins/src/write.c
|
||||
)
|
||||
|
||||
add_executable(luajit-minilua vendor/luajit/src/host/minilua.c)
|
||||
if(NOT WIN32)
|
||||
target_link_libraries(luajit-minilua m)
|
||||
endif()
|
||||
if(WIN32)
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen/buildvm_arch.h
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen
|
||||
COMMAND luajit-minilua ../dynasm/dynasm.lua -LN -D WIN -D JIT -D FFI -D P64 -o gen/buildvm_arch.h vm_x86.dasc
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
|
||||
)
|
||||
else()
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen/buildvm_arch.h
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen
|
||||
COMMAND luajit-minilua ../dynasm/dynasm.lua -LN -D WIN -D JIT -D FFI -o gen/buildvm_arch.h vm_x86.dasc
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen/buildvm_arch.h
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen
|
||||
COMMAND luajit-minilua ../dynasm/dynasm.lua -D P64 -D JIT -D FFI -D FPU -D HFABI -D VER= -o gen/buildvm_arch.h vm_x86.dasc
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
|
||||
)
|
||||
else()
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen/buildvm_arch.h
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen
|
||||
COMMAND luajit-minilua ../dynasm/dynasm.lua -D JIT -D FFI -D FPU -D HFABI -D VER= -o gen/buildvm_arch.h vm_x86.dasc
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/src/libresrc/default_config.cpp ${PROJECT_SOURCE_DIR}/src/libresrc/default_config.h
|
||||
DEPENDS ${PROJECT_SOURCE_DIR}/src/libresrc/default_config_platform.json
|
||||
COMMAND luajit-minilua ../../tools/respack.lua manifest.respack default_config.cpp default_config.h
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/libresrc
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/src/libresrc/bitmap.cpp ${PROJECT_SOURCE_DIR}/src/libresrc/bitmap.h
|
||||
COMMAND luajit-minilua ../../tools/respack.lua manifest.respack ../libresrc/bitmap.cpp ../libresrc/bitmap.h
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/bitmaps
|
||||
)
|
||||
|
||||
add_executable(luajit-buildvm
|
||||
vendor/luajit/src/host/buildvm.c
|
||||
vendor/luajit/src/host/buildvm_asm.c
|
||||
vendor/luajit/src/host/buildvm_peobj.c
|
||||
vendor/luajit/src/host/buildvm_lib.c
|
||||
vendor/luajit/src/host/buildvm_fold.c
|
||||
|
||||
vendor/luajit/src/gen/buildvm_arch.h
|
||||
)
|
||||
target_compile_definitions(luajit-buildvm PRIVATE LUAJIT_ENABLE_LUA52COMPAT)
|
||||
target_include_directories(luajit-buildvm PRIVATE vendor/luajit/src vendor/luajit/src/gen)
|
||||
if(UNIX)
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/vendor/luajit/src/lj_vm.s
|
||||
COMMAND luajit-buildvm -m elfasm -o lj_vm.s
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
|
||||
)
|
||||
elseif(MSVC)
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/vendor/luajit/src/lj_vm.obj
|
||||
COMMAND luajit-buildvm -m peobj -o lj_vm.obj
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
|
||||
)
|
||||
endif()
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen/lj_ffdef.h
|
||||
COMMAND luajit-buildvm -m ffdef -o gen/lj_ffdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen/lj_bcdef.h
|
||||
COMMAND luajit-buildvm -m bcdef -o gen/lj_bcdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen/lj_folddef.h
|
||||
COMMAND luajit-buildvm -m folddef -o gen/lj_folddef.h lj_opt_fold.c
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen/lj_recdef.h
|
||||
COMMAND luajit-buildvm -m recdef -o gen/lj_recdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/vendor/luajit/src/gen/lj_libdef.h
|
||||
COMMAND luajit-buildvm -m libdef -o gen/lj_libdef.h lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/vendor/luajit/src/jit/vmdef.lua
|
||||
COMMAND luajit-buildvm -m vmdef -o jit/vmdef.lua lib_base.c lib_math.c lib_bit.c lib_string.c lib_table.c lib_io.c lib_os.c lib_package.c lib_debug.c lib_jit.c lib_ffi.c
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/vendor/luajit/src
|
||||
)
|
||||
|
||||
add_library(luajit STATIC
|
||||
vendor/luajit/src/lj_gc.c
|
||||
vendor/luajit/src/lj_err.c
|
||||
vendor/luajit/src/lj_char.c
|
||||
vendor/luajit/src/lj_bc.c
|
||||
vendor/luajit/src/lj_obj.c
|
||||
vendor/luajit/src/lj_str.c
|
||||
vendor/luajit/src/lj_tab.c
|
||||
vendor/luajit/src/lj_func.c
|
||||
vendor/luajit/src/lj_udata.c
|
||||
vendor/luajit/src/lj_meta.c
|
||||
vendor/luajit/src/lj_debug.c
|
||||
vendor/luajit/src/lj_state.c
|
||||
vendor/luajit/src/lj_dispatch.c
|
||||
vendor/luajit/src/lj_vmevent.c
|
||||
vendor/luajit/src/lj_vmmath.c
|
||||
vendor/luajit/src/lj_strscan.c
|
||||
vendor/luajit/src/lj_api.c
|
||||
vendor/luajit/src/lj_lex.c
|
||||
vendor/luajit/src/lj_parse.c
|
||||
vendor/luajit/src/lj_bcread.c
|
||||
vendor/luajit/src/lj_bcwrite.c
|
||||
vendor/luajit/src/lj_load.c
|
||||
vendor/luajit/src/lj_ir.c
|
||||
vendor/luajit/src/lj_opt_mem.c
|
||||
vendor/luajit/src/lj_opt_fold.c
|
||||
vendor/luajit/src/lj_opt_narrow.c
|
||||
vendor/luajit/src/lj_opt_dce.c
|
||||
vendor/luajit/src/lj_opt_loop.c
|
||||
vendor/luajit/src/lj_opt_split.c
|
||||
vendor/luajit/src/lj_opt_sink.c
|
||||
vendor/luajit/src/lj_mcode.c
|
||||
vendor/luajit/src/lj_snap.c
|
||||
vendor/luajit/src/lj_record.c
|
||||
vendor/luajit/src/lj_crecord.c
|
||||
vendor/luajit/src/lj_ffrecord.c
|
||||
vendor/luajit/src/lj_asm.c
|
||||
vendor/luajit/src/lj_trace.c
|
||||
vendor/luajit/src/lj_gdbjit.c
|
||||
vendor/luajit/src/lj_ctype.c
|
||||
vendor/luajit/src/lj_cdata.c
|
||||
vendor/luajit/src/lj_cconv.c
|
||||
vendor/luajit/src/lj_ccall.c
|
||||
vendor/luajit/src/lj_ccallback.c
|
||||
vendor/luajit/src/lj_carith.c
|
||||
vendor/luajit/src/lj_clib.c
|
||||
vendor/luajit/src/lj_cparse.c
|
||||
vendor/luajit/src/lj_lib.c
|
||||
vendor/luajit/src/lj_alloc.c
|
||||
vendor/luajit/src/lib_aux.c
|
||||
vendor/luajit/src/lib_base.c
|
||||
vendor/luajit/src/lib_math.c
|
||||
vendor/luajit/src/lib_bit.c
|
||||
vendor/luajit/src/lib_string.c
|
||||
vendor/luajit/src/lib_table.c
|
||||
vendor/luajit/src/lib_io.c
|
||||
vendor/luajit/src/lib_os.c
|
||||
vendor/luajit/src/lib_package.c
|
||||
vendor/luajit/src/lib_debug.c
|
||||
vendor/luajit/src/lib_jit.c
|
||||
vendor/luajit/src/lib_ffi.c
|
||||
vendor/luajit/src/lib_init.c
|
||||
|
||||
vendor/luajit/src/gen/lj_ffdef.h
|
||||
vendor/luajit/src/gen/lj_bcdef.h
|
||||
vendor/luajit/src/gen/lj_folddef.h
|
||||
vendor/luajit/src/gen/lj_recdef.h
|
||||
vendor/luajit/src/gen/lj_libdef.h
|
||||
vendor/luajit/src/jit/vmdef.lua
|
||||
)
|
||||
target_compile_definitions(luajit PRIVATE LUAJIT_ENABLE_LUA52COMPAT)
|
||||
target_include_directories(luajit PRIVATE vendor/luajit/src/gen)
|
||||
if(MSVC)
|
||||
target_sources(luajit PRIVATE vendor/luajit/src/lj_vm.obj)
|
||||
else()
|
||||
target_sources(luajit PRIVATE vendor/luajit/src/lj_vm.s)
|
||||
set_property(SOURCE vendor/luajit/src/lj_vm.s PROPERTY LANGUAGE C)
|
||||
target_link_libraries(luajit ${CMAKE_DL_LIBS})
|
||||
endif()
|
||||
|
||||
add_library(resrc STATIC
|
||||
src/libresrc/bitmap.cpp
|
||||
src/libresrc/default_config.cpp
|
||||
src/libresrc/libresrc.cpp
|
||||
)
|
||||
|
||||
add_library(csri STATIC
|
||||
vendor/csri/lib/list.c
|
||||
vendor/csri/lib/wrap.c
|
||||
vendor/csri/subhelp/logging.c
|
||||
)
|
||||
target_include_directories(csri PRIVATE "vendor/csri/include")
|
||||
if(WIN32)
|
||||
target_include_directories(csri PRIVATE "vendor/csri/lib/win32")
|
||||
target_sources(csri PRIVATE vendor/csri/lib/win32/enumerate.c)
|
||||
else()
|
||||
target_include_directories(csri PRIVATE "vendor/csri/lib/posix")
|
||||
target_sources(csri PRIVATE vendor/csri/lib/posix/enumerate.c)
|
||||
endif()
|
||||
|
||||
add_executable(Aegisub WIN32
|
||||
src/command/app.cpp
|
||||
src/command/audio.cpp
|
||||
src/command/automation.cpp
|
||||
src/command/command.cpp
|
||||
src/command/edit.cpp
|
||||
src/command/grid.cpp
|
||||
src/command/help.cpp
|
||||
src/command/keyframe.cpp
|
||||
src/command/recent.cpp
|
||||
src/command/subtitle.cpp
|
||||
src/command/time.cpp
|
||||
src/command/timecode.cpp
|
||||
src/command/tool.cpp
|
||||
src/command/video.cpp
|
||||
src/command/vis_tool.cpp
|
||||
src/dialog_about.cpp
|
||||
src/dialog_align.cpp
|
||||
src/dialog_attachments.cpp
|
||||
src/dialog_automation.cpp
|
||||
src/dialog_autosave.cpp
|
||||
src/dialog_colorpicker.cpp
|
||||
src/dialog_detached_video.cpp
|
||||
src/dialog_dummy_video.cpp
|
||||
src/dialog_export.cpp
|
||||
src/dialog_export_ebu3264.cpp
|
||||
src/dialog_fonts_collector.cpp
|
||||
src/dialog_jumpto.cpp
|
||||
src/dialog_kara_timing_copy.cpp
|
||||
src/dialog_log.cpp
|
||||
src/dialog_paste_over.cpp
|
||||
src/dialog_progress.cpp
|
||||
src/dialog_properties.cpp
|
||||
src/dialog_resample.cpp
|
||||
src/dialog_search_replace.cpp
|
||||
src/dialog_selected_choices.cpp
|
||||
src/dialog_selection.cpp
|
||||
src/dialog_shift_times.cpp
|
||||
src/dialog_spellchecker.cpp
|
||||
src/dialog_style_editor.cpp
|
||||
src/dialog_style_manager.cpp
|
||||
src/dialog_styling_assistant.cpp
|
||||
src/dialog_text_import.cpp
|
||||
src/dialog_timing_processor.cpp
|
||||
src/dialog_translation.cpp
|
||||
src/dialog_video_details.cpp
|
||||
src/dialog_video_properties.cpp
|
||||
src/subtitle_format.cpp
|
||||
src/subtitle_format_ass.cpp
|
||||
src/subtitle_format_ebu3264.cpp
|
||||
src/subtitle_format_encore.cpp
|
||||
src/subtitle_format_microdvd.cpp
|
||||
src/subtitle_format_mkv.cpp
|
||||
src/subtitle_format_srt.cpp
|
||||
src/subtitle_format_ssa.cpp
|
||||
src/subtitle_format_transtation.cpp
|
||||
src/subtitle_format_ttxt.cpp
|
||||
src/subtitle_format_txt.cpp
|
||||
src/visual_tool.cpp
|
||||
src/visual_tool_clip.cpp
|
||||
src/visual_tool_cross.cpp
|
||||
src/visual_tool_drag.cpp
|
||||
src/visual_tool_rotatexy.cpp
|
||||
src/visual_tool_rotatez.cpp
|
||||
src/visual_tool_scale.cpp
|
||||
src/visual_tool_vector_clip.cpp
|
||||
src/MatroskaParser.c
|
||||
src/aegisublocale.cpp
|
||||
src/ass_attachment.cpp
|
||||
src/ass_dialogue.cpp
|
||||
src/ass_entry.cpp
|
||||
src/ass_export_filter.cpp
|
||||
src/ass_exporter.cpp
|
||||
src/ass_file.cpp
|
||||
src/ass_karaoke.cpp
|
||||
src/ass_override.cpp
|
||||
src/ass_parser.cpp
|
||||
src/ass_style.cpp
|
||||
src/ass_style_storage.cpp
|
||||
src/async_video_provider.cpp
|
||||
src/audio_box.cpp
|
||||
src/audio_colorscheme.cpp
|
||||
src/audio_controller.cpp
|
||||
src/audio_display.cpp
|
||||
src/audio_karaoke.cpp
|
||||
src/audio_marker.cpp
|
||||
src/audio_player.cpp
|
||||
src/audio_provider_factory.cpp
|
||||
src/audio_renderer.cpp
|
||||
src/audio_renderer_spectrum.cpp
|
||||
src/audio_renderer_waveform.cpp
|
||||
src/audio_timing_dialogue.cpp
|
||||
src/audio_timing_karaoke.cpp
|
||||
src/auto4_base.cpp
|
||||
src/auto4_lua.cpp
|
||||
src/auto4_lua_assfile.cpp
|
||||
src/auto4_lua_dialog.cpp
|
||||
src/auto4_lua_progresssink.cpp
|
||||
src/base_grid.cpp
|
||||
src/charset_detect.cpp
|
||||
src/colorspace.cpp
|
||||
src/colour_button.cpp
|
||||
src/compat.cpp
|
||||
src/context.cpp
|
||||
src/export_fixstyle.cpp
|
||||
src/export_framerate.cpp
|
||||
src/fft.cpp
|
||||
src/font_file_lister.cpp
|
||||
src/frame_main.cpp
|
||||
src/gl_text.cpp
|
||||
src/gl_wrap.cpp
|
||||
src/grid_column.cpp
|
||||
src/help_button.cpp
|
||||
src/hotkey.cpp
|
||||
src/hotkey_data_view_model.cpp
|
||||
src/image_position_picker.cpp
|
||||
src/initial_line_state.cpp
|
||||
src/main.cpp
|
||||
src/menu.cpp
|
||||
src/mkv_wrap.cpp
|
||||
src/pen.cpp
|
||||
src/persist_location.cpp
|
||||
src/preferences.cpp
|
||||
src/preferences_base.cpp
|
||||
src/project.cpp
|
||||
src/resolution_resampler.cpp
|
||||
src/search_replace_engine.cpp
|
||||
src/selection_controller.cpp
|
||||
src/spellchecker.cpp
|
||||
src/spline.cpp
|
||||
src/spline_curve.cpp
|
||||
src/string_codec.cpp
|
||||
src/subs_controller.cpp
|
||||
src/subs_edit_box.cpp
|
||||
src/subs_edit_ctrl.cpp
|
||||
src/subs_preview.cpp
|
||||
src/subtitles_provider.cpp
|
||||
src/subtitles_provider_libass.cpp
|
||||
src/text_file_reader.cpp
|
||||
src/text_file_writer.cpp
|
||||
src/text_selection_controller.cpp
|
||||
src/thesaurus.cpp
|
||||
src/timeedit_ctrl.cpp
|
||||
src/toggle_bitmap.cpp
|
||||
src/toolbar.cpp
|
||||
src/tooltip_manager.cpp
|
||||
src/utils.cpp
|
||||
src/validators.cpp
|
||||
src/vector2d.cpp
|
||||
src/version.cpp
|
||||
src/video_box.cpp
|
||||
src/video_controller.cpp
|
||||
src/video_display.cpp
|
||||
src/video_frame.cpp
|
||||
src/video_out_gl.cpp
|
||||
src/video_provider_cache.cpp
|
||||
src/video_provider_dummy.cpp
|
||||
src/video_provider_manager.cpp
|
||||
src/video_provider_yuv4mpeg.cpp
|
||||
src/video_slider.cpp
|
||||
src/visual_feature.cpp
|
||||
)
|
||||
target_link_libraries(Aegisub ${CMAKE_DL_LIBS} libaegisub luabins luajit resrc csri)
|
||||
target_compile_definitions(Aegisub PRIVATE CMAKE_BUILD)
|
||||
|
||||
set(WITH_BUILD_CREDIT OFF CACHE BOOL "Whether show build credit in about dialog")
|
||||
if(WITH_BUILD_CREDIT)
|
||||
set(BUILD_CREDIT "" CACHE STRING "Build credit shown in about dialog")
|
||||
target_compile_definitions(Aegisub PRIVATE "BUILD_CREDIT=${BUILD_CREDIT}")
|
||||
else()
|
||||
unset(BUILD_CREDIT CACHE)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
set_target_properties(libaegisub PROPERTIES COMPILE_FLAGS "/Yu${PROJECT_SOURCE_DIR}/libaegisub/lagi_pre.h" COMPILE_FLAGS "/FI${PROJECT_SOURCE_DIR}/libaegisub/lagi_pre.h")
|
||||
else()
|
||||
target_compile_options(libaegisub PRIVATE -include "${PROJECT_SOURCE_DIR}/libaegisub/lagi_pre.h")
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
add_definitions("/DNOMINMAX /MP /DINITGUID")
|
||||
set_target_properties(Aegisub PROPERTIES COMPILE_FLAGS "/Yu${PROJECT_SOURCE_DIR}/src/agi_pre.h" COMPILE_FLAGS "/FI${PROJECT_SOURCE_DIR}/src/agi_pre.h")
|
||||
target_link_libraries(Aegisub Usp10)
|
||||
#target_sources(Aegisub PRIVATE src/res/res.rc src/res/strings.rc src/crash_writer_minidump.cpp)
|
||||
target_sources(Aegisub PRIVATE src/res/res.rc src/res/strings.rc src/crash_writer.cpp src/dpi_aware.manifest)
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Aegisub)
|
||||
else()
|
||||
target_sources(Aegisub PRIVATE src/crash_writer.cpp)
|
||||
target_compile_options(Aegisub PRIVATE -include "${PROJECT_SOURCE_DIR}/src/agi_pre.h")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_sources(Aegisub PRIVATE src/font_file_lister_gdi.cpp)
|
||||
else()
|
||||
find_package(Fontconfig REQUIRED)
|
||||
target_include_directories(Aegisub PRIVATE ${Fontconfig_INCLUDE_DIRS})
|
||||
target_link_libraries(Aegisub ${Fontconfig_LIBRARIES})
|
||||
target_sources(Aegisub PRIVATE src/font_file_lister_fontconfig.cpp)
|
||||
endif()
|
||||
|
||||
find_package(ass REQUIRED)
|
||||
target_include_directories(Aegisub PRIVATE ${ass_INCLUDE_DIRS})
|
||||
target_link_libraries(Aegisub ${ass_LIBRARIES})
|
||||
|
||||
find_package(Boost REQUIRED chrono filesystem locale regex system thread)
|
||||
target_include_directories(libaegisub PRIVATE ${Boost_INCLUDE_DIRS})
|
||||
target_include_directories(Aegisub PRIVATE ${Boost_INCLUDE_DIRS})
|
||||
target_link_directories(Aegisub PRIVATE ${Boost_LIBRARY_DIRS})
|
||||
target_link_libraries(Aegisub ${Boost_LIBRARIES})
|
||||
|
||||
find_package(OpenGL REQUIRED)
|
||||
target_include_directories(Aegisub PRIVATE ${OPENGL_INCLUDE_DIR})
|
||||
target_link_libraries(Aegisub ${OPENGL_LIBRARIES})
|
||||
|
||||
find_package(Iconv REQUIRED)
|
||||
target_compile_definitions(libaegisub PRIVATE "HAVE_ICONV")
|
||||
target_include_directories(libaegisub PRIVATE ${Iconv_INCLUDE_DIRS})
|
||||
target_link_libraries(libaegisub ${Iconv_LIBRARIES})
|
||||
if(NOT Iconv_IS_BUILT_IN)
|
||||
target_compile_definitions(libaegisub PRIVATE "AGI_ICONV_CONST")
|
||||
endif()
|
||||
|
||||
find_package(ICU REQUIRED uc dt in)
|
||||
target_include_directories(libaegisub PRIVATE ${ICU_INCLUDE_DIRS})
|
||||
target_include_directories(Aegisub PRIVATE ${ICU_INCLUDE_DIRS})
|
||||
target_link_libraries(Aegisub ${ICU_LIBRARIES})
|
||||
|
||||
find_package(wxWidgets REQUIRED adv base core gl stc xml)
|
||||
include(${wxWidgets_USE_FILE})
|
||||
target_link_libraries(Aegisub ${wxWidgets_LIBRARIES})
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
target_include_directories(Aegisub PRIVATE ${ZLIB_INCLUDE_DIRS})
|
||||
target_link_libraries(Aegisub ${ZLIB_LIBRARIES})
|
||||
|
||||
set(WITH_ALSA ON CACHE BOOL "Enable ALSA support")
|
||||
if(WITH_ALSA)
|
||||
find_package(ALSA)
|
||||
if(NOT ALSA_FOUND)
|
||||
set(WITH_ALSA OFF CACHE BOOL "Enable ALSA support" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
if(WITH_ALSA)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_ALSA")
|
||||
target_include_directories(Aegisub PRIVATE ${ALSA_INCLUDE_DIRS})
|
||||
target_link_libraries(Aegisub ${ALSA_LIBRARIES})
|
||||
target_sources(Aegisub PRIVATE src/audio_player_alsa.cpp)
|
||||
endif()
|
||||
|
||||
set(WITH_AVISYNTH ON CACHE BOOL "Enable AviSynth support")
|
||||
if(WITH_AVISYNTH)
|
||||
find_package(AviSynth)
|
||||
if(NOT AviSynth_FOUND)
|
||||
set(WITH_AVISYNTH OFF CACHE BOOL "Enable AviSynth support" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
if(WITH_AVISYNTH)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_AVISYNTH" "AVS_LINKAGE_DLLIMPORT")
|
||||
target_include_directories(Aegisub PRIVATE ${AviSynth_INCLUDE_DIRS})
|
||||
target_link_libraries(Aegisub Vfw32 ${AviSynth_LIBRARIES})
|
||||
target_sources(Aegisub PRIVATE src/audio_provider_avs.cpp src/avisynth_wrap.cpp src/video_provider_avs.cpp)
|
||||
endif()
|
||||
|
||||
set(WITH_CSRI ON CACHE BOOL "Enable CSRI support")
|
||||
if(WITH_CSRI)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_CSRI")
|
||||
target_include_directories(Aegisub PRIVATE "${PROJECT_SOURCE_DIR}/vendor/csri/include")
|
||||
target_sources(Aegisub PRIVATE src/subtitles_provider_csri.cpp)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_DIRECTSOUND")
|
||||
target_link_libraries(Aegisub dsound)
|
||||
target_sources(Aegisub PRIVATE src/audio_player_dsound.cpp src/audio_player_dsound2.cpp)
|
||||
set(WITH_DIRECTSOUND ON)
|
||||
set(WITH_XAUDIO2 ON CACHE BOOL "Enable XAudio2 support")
|
||||
if(WITH_XAUDIO2)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_XAUDIO2")
|
||||
target_sources(Aegisub PRIVATE src/audio_player_xaudio2.cpp)
|
||||
endif()
|
||||
else()
|
||||
set(WITH_DIRECTSOUND OFF)
|
||||
set(WITH_XAUDIO2 OFF)
|
||||
endif()
|
||||
|
||||
set(WITH_FFMS2 ON CACHE BOOL "Enable FFMS2 support")
|
||||
if(WITH_FFMS2)
|
||||
find_package(FFMS2)
|
||||
if(NOT FFMS2_FOUND)
|
||||
set(WITH_FFMS2 OFF CACHE BOOL "Enable FFMS2 support" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
if(WITH_FFMS2)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_FFMS2")
|
||||
target_include_directories(Aegisub PRIVATE ${FFMS2_INCLUDE_DIRS})
|
||||
target_link_libraries(Aegisub ${FFMS2_LIBRARIES})
|
||||
target_sources(Aegisub PRIVATE src/audio_provider_ffmpegsource.cpp src/ffmpegsource_common.cpp src/video_provider_ffmpegsource.cpp)
|
||||
else()
|
||||
message(SEND_ERROR
|
||||
"No supported video/audio reader interface was enabled.\n"
|
||||
"You will not be able to open any video or audio files in Aegisub unless you install a supported video/audio provider.\n"
|
||||
"You will however still be able to open \"dummy\" video, ie. a blank, virtual video clip with subtitles overlaid.\n"
|
||||
"Currently we only support one video/audio provider on non-Windows systems:\n"
|
||||
" - FFMS2\n"
|
||||
" * http://github.com/FFMS/ffms2\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
set(WITH_FFTW3 ON CACHE BOOL "Enable fftw support")
|
||||
if(WITH_FFTW3)
|
||||
find_package(FFTW)
|
||||
if(NOT FFTW_FOUND)
|
||||
set(WITH_FFTW3 OFF CACHE BOOL "Enable fftw support" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
if(WITH_FFTW3)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_FFTW3")
|
||||
target_include_directories(Aegisub PRIVATE ${FFTW_INCLUDES})
|
||||
target_link_libraries(Aegisub ${FFTW_LIBRARIES})
|
||||
endif()
|
||||
|
||||
set(WITH_HUNSPELL ON CACHE BOOL "Enable Hunspell support")
|
||||
if(WITH_HUNSPELL)
|
||||
find_package(Hunspell)
|
||||
if(NOT HUNSPELL_FOUND)
|
||||
set(WITH_HUNSPELL OFF CACHE BOOL "Enable Hunspell support" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
if(WITH_HUNSPELL)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_HUNSPELL")
|
||||
target_include_directories(Aegisub PRIVATE ${HUNSPELL_INCLUDE_DIR})
|
||||
target_link_libraries(Aegisub ${HUNSPELL_LIBRARIES})
|
||||
target_sources(Aegisub PRIVATE src/spellchecker_hunspell.cpp)
|
||||
if(HUNSPELL_HAS_STRING_API)
|
||||
target_compile_definitions(Aegisub PRIVATE "HUNSPELL_HAS_STRING_API")
|
||||
endif(HUNSPELL_HAS_STRING_API)
|
||||
endif()
|
||||
|
||||
set(WITH_LIBPULSE ON CACHE BOOL "Enable PulseAudio support")
|
||||
if(WITH_LIBPULSE)
|
||||
find_package(PulseAudio)
|
||||
if(NOT PULSEAUDIO_FOUND)
|
||||
set(WITH_LIBPULSE OFF CACHE BOOL "Enable PulseAudio support" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
if(WITH_LIBPULSE)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_LIBPULSE")
|
||||
target_include_directories(Aegisub PRIVATE ${PULSEAUDIO_INCLUDE_DIR})
|
||||
target_link_libraries(Aegisub ${PULSEAUDIO_LIBRARY})
|
||||
target_sources(Aegisub PRIVATE src/audio_player_pulse.cpp)
|
||||
endif()
|
||||
|
||||
set(WITH_OPENAL ON CACHE BOOL "Enable OpenAL support")
|
||||
if(WITH_OPENAL)
|
||||
find_package(OpenAL)
|
||||
if(NOT OPENAL_FOUND)
|
||||
set(WITH_OPENAL OFF CACHE BOOL "Enable OpenAL support" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
if(WITH_OPENAL)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_OPENAL")
|
||||
target_include_directories(Aegisub PRIVATE ${OPENAL_INCLUDE_DIR})
|
||||
target_link_libraries(Aegisub ${OPENAL_LIBRARY})
|
||||
target_sources(Aegisub PRIVATE src/audio_player_openal.cpp)
|
||||
endif()
|
||||
|
||||
set(WITH_OSS OFF CACHE BOOL "Enable OSS support")
|
||||
if(WITH_OSS)
|
||||
find_package(OSS)
|
||||
if(NOT OSS_FOUND)
|
||||
set(WITH_OSS OFF CACHE BOOL "Enable OSS support" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
if(WITH_OSS)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_OSS")
|
||||
target_include_directories(Aegisub PRIVATE ${OSS_INCLUDE_DIRS})
|
||||
target_sources(Aegisub PRIVATE src/audio_player_oss.cpp)
|
||||
endif()
|
||||
|
||||
set(WITH_PORTAUDIO ON CACHE BOOL "Enable PortAudio support")
|
||||
if(WITH_PORTAUDIO)
|
||||
find_package(PortAudio)
|
||||
if(NOT PortAudio_FOUND)
|
||||
set(WITH_PORTAUDIO OFF CACHE BOOL "Enable PortAudio support" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
if(WITH_PORTAUDIO)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_PORTAUDIO")
|
||||
target_include_directories(Aegisub PRIVATE ${PortAudio_INCLUDE_DIRS})
|
||||
target_link_libraries(Aegisub ${PortAudio_LIBRARIES})
|
||||
target_sources(Aegisub PRIVATE src/audio_player_portaudio.cpp)
|
||||
endif()
|
||||
|
||||
set(WITH_STARTUPLOG OFF CACHE BOOL "Enable startup log")
|
||||
if(WITH_STARTUPLOG)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_STARTUPLOG")
|
||||
endif()
|
||||
|
||||
set(WITH_UCHARDET ON CACHE BOOL "Enable uchardet support")
|
||||
if(WITH_UCHARDET)
|
||||
find_package(uchardet)
|
||||
if(NOT uchardet_FOUND)
|
||||
set(WITH_UCHARDET OFF CACHE BOOL "Enable uchardet support" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
if(WITH_UCHARDET)
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_UCHARDET")
|
||||
target_include_directories(Aegisub PRIVATE ${uchardet_INCLUDE_DIRS})
|
||||
target_link_libraries(Aegisub ${uchardet_LIBRARIES})
|
||||
endif()
|
||||
|
||||
set(WITH_UPDATE_CHECKER OFF)
|
||||
if(WITH_UPDATE_CHECKER)
|
||||
set(UPDATE_CHECKER_SERVER "\"updates.aegisub.org\"" CACHE STRING "Server for the update checker")
|
||||
set(UPDATE_CHECKER_BASE_URL "\"/trunk\"" CACHE STRING "Base path for the update checker")
|
||||
target_compile_definitions(Aegisub PRIVATE "WITH_UPDATE_CHECKER" "UPDATE_CHECKER_SERVER=${UPDATE_CHECKER_SERVER}" "UPDATE_CHECKER_BASE_URL=${UPDATE_CHECKER_BASE_URL}")
|
||||
target_link_libraries(Aegisub ${Boost_asio_LIBRARY})
|
||||
target_sources(Aegisub PRIVATE src/dialog_version_check.cpp)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(DEFAULT_PLAYER_AUDIO DirectSound)
|
||||
configure_file("src/libresrc/default_config_win.json" "${PROJECT_SOURCE_DIR}/src/libresrc/default_config_platform.json" COPYONLY)
|
||||
else()
|
||||
if(WITH_LIBPULSE)
|
||||
set(DEFAULT_PLAYER_AUDIO "PulseAudio" CACHE STRING "Default audio player")
|
||||
elseif(WITH_ALSA)
|
||||
set(DEFAULT_PLAYER_AUDIO "ALSA" CACHE STRING "Default audio player")
|
||||
elseif(WITH_OPENAL)
|
||||
set(DEFAULT_PLAYER_AUDIO "OpenAL" CACHE STRING "Default audio player")
|
||||
elseif(WITH_PORTAUDIO)
|
||||
set(DEFAULT_PLAYER_AUDIO "PortAudio" CACHE STRING "Default audio player")
|
||||
elseif(WITH_OSS)
|
||||
set(DEFAULT_PLAYER_AUDIO "OSS" CACHE STRING "Default audio player")
|
||||
else()
|
||||
message(SEND_ERROR
|
||||
"No supported audio player interface was enabled.\n"
|
||||
"If you want audio support in Aegisub you need to install one of these libraries:\n"
|
||||
" - PulseAudio\n"
|
||||
" * http://pulseaudio.org/\n"
|
||||
" - ALSA (Linux only)\n"
|
||||
" * http://www.alsa-project.org/\n"
|
||||
" - PortAudio (version 19 only)\n"
|
||||
" * http://www.portaudio.com/\n"
|
||||
"\n"
|
||||
)
|
||||
set(DEFAULT_PLAYER_AUDIO "NONE" CACHE STRING "Default audio player")
|
||||
endif()
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_SOURCE_DIR}/src/libresrc/default_config_platform.json
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/src/libresrc/default_config_win.json ${PROJECT_SOURCE_DIR}/src/libresrc/default_config_platform.json
|
||||
)
|
||||
configure_file("src/libresrc/default_config_platform.json.in" "${PROJECT_SOURCE_DIR}/src/libresrc/default_config_platform.json" @ONLY)
|
||||
endif()
|
||||
|
||||
if(NOT WIN32)
|
||||
set(AEGISUB_COMMAND "aegisub" CACHE STRING "The executable name of Aegisub")
|
||||
set_target_properties(Aegisub PROPERTIES OUTPUT_NAME "${AEGISUB_COMMAND}")
|
||||
configure_file("packages/desktop/aegisub.desktop.template.in" "aegisub.desktop" @ONLY)
|
||||
install(FILES "${CMAKE_BINARY_DIR}/aegisub.desktop" DESTINATION "share/applications")
|
||||
install(FILES "packages/desktop/16x16.png" DESTINATION "share/icons/hicolor/16x16/apps" RENAME aegisub.png)
|
||||
install(FILES "packages/desktop/22x22.png" DESTINATION "share/icons/hicolor/22x22/apps" RENAME aegisub.png)
|
||||
install(FILES "packages/desktop/24x24.png" DESTINATION "share/icons/hicolor/24x24/apps" RENAME aegisub.png)
|
||||
install(FILES "packages/desktop/32x32.png" DESTINATION "share/icons/hicolor/32x32/apps" RENAME aegisub.png)
|
||||
install(FILES "packages/desktop/48x48.png" DESTINATION "share/icons/hicolor/48x48/apps" RENAME aegisub.png)
|
||||
install(FILES "packages/desktop/64x64.png" DESTINATION "share/icons/hicolor/64x64/apps" RENAME aegisub.png)
|
||||
install(FILES "packages/desktop/scalable.svg" DESTINATION "share/icons/hicolor/scalable/apps" RENAME aegisub.svg)
|
||||
endif()
|
||||
install(TARGETS Aegisub DESTINATION bin)
|
||||
|
||||
set(WITH_TEST OFF CACHE BOOL "Enable unit tests")
|
||||
if(WITH_TEST)
|
||||
include("CMakeLists.test.txt")
|
||||
endif()
|
||||
|
||||
message(STATUS "\n"
|
||||
"Configure settings\n"
|
||||
" Install prefix: ${CMAKE_INSTALL_PREFIX}\n"
|
||||
" CFLAGS ${CMAKE_C_FLAGS}\n"
|
||||
" CXXFLAGS ${CMAKE_CXX_FLAGS}\n"
|
||||
"\n"
|
||||
"Default Settings\n"
|
||||
" Audio Player: ${DEFAULT_PLAYER_AUDIO}\n"
|
||||
"\n"
|
||||
"Audio Players\n"
|
||||
" ALSA: ${WITH_ALSA}\n"
|
||||
" DirectSound: ${WITH_DIRECTSOUND}\n"
|
||||
" DirectSound-old: ${WITH_DIRECTSOUND}\n"
|
||||
" XAudio2: ${WITH_XAUDIO2}\n"
|
||||
" OpenAL: ${WITH_OPENAL}\n"
|
||||
" OSS: ${WITH_OSS}\n"
|
||||
" PortAudio: ${WITH_PORTAUDIO}\n"
|
||||
" PulseAudio: ${WITH_LIBPULSE}\n"
|
||||
"\n"
|
||||
"Misc Packages\n"
|
||||
" AviSynth: ${WITH_AVISYNTH}\n"
|
||||
" CSRI: ${WITH_CSRI}\n"
|
||||
" FFMS2: ${WITH_FFMS2}\n"
|
||||
" FFTW3: ${WITH_FFTW3}\n"
|
||||
" Hunspell: ${WITH_HUNSPELL}\n"
|
||||
" uchardet: ${WITH_UCHARDET}\n"
|
||||
" LuaJIT: bundled\n"
|
||||
"\n"
|
||||
"Options\n"
|
||||
" Startup log: ${WITH_STARTUPLOG}\n"
|
||||
" Update checker: ${WITH_UPDATE_CHECKER}\n"
|
||||
" Tests: ${WITH_TEST}\n"
|
||||
"\n"
|
||||
)
|
|
@ -54,6 +54,7 @@ P_BINDIR = @bindir@
|
|||
P_DATAROOT = @datarootdir@
|
||||
P_LOCALE = @localedir@
|
||||
|
||||
P_APPDATA = @P_APPDATA@
|
||||
P_DESKTOP = @P_DESKTOP@
|
||||
P_ICON = @P_ICON@
|
||||
P_DATA = $(P_DATAROOT)/aegisub/
|
||||
|
|
|
@ -7,7 +7,7 @@ COMMANDS := all install clean distclean test depclean osx-bundle osx-dmg test-au
|
|||
ifeq (yes, $(BUILD_DARWIN))
|
||||
CFLAGS += -mmacosx-version-min=10.8 -gfull
|
||||
CXXFLAGS += -mmacosx-version-min=10.8 -gfull
|
||||
LDFLAGS += -mmacosx-version-min=10.8 -Wl,-dead_strip -pagezero_size 10000 -image_base 100000000
|
||||
LDFLAGS += -mmacosx-version-min=10.8 -Wl,-dead_strip
|
||||
LIB_SHARED_LINK = $(LIB_SHARED_LINK_OSX)
|
||||
endif
|
||||
|
||||
|
|
95
README.md
95
README.md
|
@ -1,85 +1,40 @@
|
|||
[![Build Status](https://travis-ci.org/wangqr/Aegisub.svg?branch=dev)](https://travis-ci.org/wangqr/Aegisub)
|
||||
|
||||
# Aegisub
|
||||
|
||||
For binaries and general information [see the homepage](http://www.aegisub.org).
|
||||
|
||||
The bug tracker can be found at http://devel.aegisub.org.
|
||||
The bug tracker can be found at https://github.com/Aegisub/Aegisub/issues .
|
||||
|
||||
Support is available on [the forums](http://forum.aegisub.org) or [on IRC](irc://irc.rizon.net/aegisub).
|
||||
Support is available on IRC ( irc://irc.rizon.net/aegisub ).
|
||||
|
||||
## Building Aegisub
|
||||
|
||||
### Windows
|
||||
|
||||
Prerequisites:
|
||||
|
||||
1. Visual Studio 2015 (the free Community edition is good enough)
|
||||
2. The June 2010 DirectX SDK (the final release before DirectSound was dropped)
|
||||
3. [Yasm](http://yasm.tortall.net/) installed to somewhere on your path.
|
||||
|
||||
There are a few optional dependencies:
|
||||
|
||||
1. msgfmt, to build the translations
|
||||
2. WinRAR, to build the portable installer
|
||||
3. InnoSetup, to build the regular installer
|
||||
|
||||
All other dependencies are either stored in the repository or are included as submodules.
|
||||
1. CMake 3.14 or later (or you can use an older version by editing the first line in CMakeLists.txt, and download the missing `cmake/FindFontconfig.cmake` from [here](https://gitlab.kitware.com/cmake/cmake/blob/master/Modules/FindFontconfig.cmake)),
|
||||
2. Any compiling toolchain supported by CMake,
|
||||
3. All required dependencies, namely `libass`, `Boost`(with ICU support), `OpenGL`, `libicu`, `wxWidgets`, `zlib`. Additionally, `libiconv` is required on non-POSIX systems. `fontconfig` is required on non-Windows systems.
|
||||
4. Any optional dependencies, namely `ALSA`, `FFMS2`, `FFTW`, `Hunspell`, `OpenAL`, `uchardet`.
|
||||
|
||||
Building:
|
||||
|
||||
1. Clone Aegisub's repository recursively to fetch it and all submodules: `git clone --recursive git@github.com:Aegisub/Aegisub.git` This will take quite a while and requires about 2.5 GB of disk space.
|
||||
2. Open Aegisub.sln
|
||||
3. Build the BuildTasks project.
|
||||
4. Build the entire solution.
|
||||
|
||||
You should now have a `bin` directory in your Aegisub directory which contains `aegisub32d.exe`, along with a pile of other files.
|
||||
|
||||
The Aegisub installer includes some files not built as part of Aegisub (such as Avisynth and VSFilter), so for a fully functional copy of Aegisub you now need to copy all of the files from an installed copy of Aegisub into your `bin` directory (and don't overwrite any of the files already there).
|
||||
You'll also either need to copy the `automation` directory into the `bin` directory, or edit your automation search paths to include the `automation` directory in the source tree.
|
||||
|
||||
After building the solution once, you'll want to switch to the Debug-MinDep configuration, which skips checking if the dependencies are out of date, as that takes a while.
|
||||
|
||||
### OS X
|
||||
|
||||
A vaguely recent version of Xcode and the corresponding command-line tools are required.
|
||||
Nothing older than Xcode 5 has been tested recently, but it is likely that some later versions of Xcode 4 are good enough.
|
||||
|
||||
For personal usage, you can use homebrew to install almost all of Aegisub's dependencies:
|
||||
|
||||
brew install autoconf ffmpeg freetype gettext ffms2 fftw fribidi libass m4
|
||||
brew install --devel --with-gc64 luajit
|
||||
brew install --HEAD icu4c
|
||||
brew link --force icu4c
|
||||
brew link --force gettext
|
||||
brew install --HEAD --c++11 --with-icu4c boost
|
||||
|
||||
wxWidgets is located in vendor/wxWidgets, and can be built like so:
|
||||
|
||||
CPPFLAGS="$CPPFLAGS -D__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES=1" \
|
||||
./configure --disable-aboutdlg --disable-animatectrl --disable-aui --disable-any \
|
||||
--disable-bannerwindow --disable-base64 --disable-calendar --disable-caret \
|
||||
--disable-cmdline --disable-colourpicker --disable-compat28 --disable-config \
|
||||
--disable-constraints --disable-datepick --disable-dctransform --disable-debugreport \
|
||||
--disable-dialupman --disable-docview --disable-filehistory --disable-finddlg \
|
||||
--disable-fs_archive --disable-fs_inet --disable-fs_zip --disable-fsvolume \
|
||||
--disable-fswatcher --disable-gif --disable-help --disable-html --disable-ipc \
|
||||
--disable-joystick --disable-jpeg --disable-largefile --disable-markup --disable-mdi \
|
||||
--disable-mediactrl --disable-metafiles --disable-miniframe --disable-notifmsg \
|
||||
--disable-numberdlg --disable-pcx --disable-pnm --disable-postscript \
|
||||
--disable-prefseditor --disable-printarch --disable-progressdlg --disable-propgrid \
|
||||
--disable-protocol --disable-protocols --disable-rearrangectrl --disable-ribbon \
|
||||
--disable-richtext --disable-richtooltip --disable-snglinst --disable-sockets \
|
||||
--disable-sockets --disable-sound --disable-splash --disable-splines \
|
||||
--disable-std_iostreams --disable-svg --disable-tarstream --disable-tiff \
|
||||
--disable-tipdlg --disable-tipwindow --disable-url --disable-webkit --disable-webview \
|
||||
--disable-wizarddlg --disable-xrc \
|
||||
--enable-geometry --enable-imaglist --enable-listctrl --enable-stc --with-cocoa \
|
||||
--with-libpng=yes --with-macosx-version-min=10.9 \
|
||||
--with-opengl \
|
||||
--without-libjpeg --without-libtiff --without-regex \
|
||||
&& make
|
||||
|
||||
Once the dependencies are installed, build Aegisub with `autoreconf && ./configure --with-wxdir=/path/to/Aegisub/vendor/wxWidgets && make && make osx-bundle`.
|
||||
`autoreconf` should be skipped if you are building from a source tarball rather than `git`.
|
||||
1. If you decided to build from source:
|
||||
```shell
|
||||
git clone https://github.com/wangqr/Aegisub.git # No --recursive is needed
|
||||
cd Aegisub
|
||||
./build/version.sh . # This will generate build/git_version.h
|
||||
```
|
||||
2. Make an empty directory to hold build outputs:
|
||||
```shell
|
||||
mkdir build-dir
|
||||
```
|
||||
3. Build the project using CMake. Use either cmake-gui, or the command line:
|
||||
```shell
|
||||
cd build-dir
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
## Updating Moonscript
|
||||
|
||||
|
@ -95,4 +50,4 @@ The file is now ready for use, to be placed in `automation/include` within the A
|
|||
## License
|
||||
|
||||
All files in this repository are licensed under various GPL-compatible BSD-style licenses; see LICENCE and the individual source files for more information.
|
||||
The official Windows and OS X builds are GPLv2 due to including fftw3.
|
||||
The official Windows build is GPLv2 due to including fftw3.
|
||||
|
|
|
@ -39,7 +39,7 @@ search = (re, str, start) ->
|
|||
res = regex.search re, str, str\len(), start
|
||||
return unless res != nil
|
||||
first, last = res[0], res[1]
|
||||
ffi.C.free res
|
||||
ffi.gc(res, ffi.C.free)
|
||||
first, last
|
||||
|
||||
replace = (re, replacement, str, max_count) ->
|
||||
|
|
|
@ -40,3 +40,11 @@ Returns: 2 values, all numbers.
|
|||
2. End of the selection, in milliseconds.
|
||||
|
||||
---
|
||||
|
||||
Setting the main frame's status bar text
|
||||
|
||||
function aegisub.set_status_bar_text(text)
|
||||
|
||||
Returns: 0 values
|
||||
|
||||
---
|
||||
|
|
|
@ -30,6 +30,9 @@
|
|||
<AdditionalLibraryDirectories Condition="'$(Platform)'=='Win32'">$(DXSDK_DIR)\Lib\x86</AdditionalLibraryDirectories>
|
||||
<AdditionalLibraryDirectories Condition="'$(Platform)'=='x64'">$(DXSDK_DIR)\Lib\x64</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<EnableDpiAwareness>PerMonitorHighDPIAware</EnableDpiAwareness>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- Update git_version.h -->
|
||||
|
|
20
cmake/FindAviSynth.cmake
Normal file
20
cmake/FindAviSynth.cmake
Normal file
|
@ -0,0 +1,20 @@
|
|||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_AviSynth QUIET AviSynth)
|
||||
find_path(AviSynth_INCLUDE_DIRS
|
||||
NAMES avisynth.h
|
||||
HINTS ${PC_AviSynth_INCLUDE_DIRS}
|
||||
)
|
||||
find_library(AviSynth_LIBRARIES
|
||||
NAMES avisynth
|
||||
PATH_SUFFIXES c_api
|
||||
HINTS ${PC_AviSynth_LIBRARY_DIRS}
|
||||
)
|
||||
set(AviSynth_VERSION ${PC_AviSynth_VERSION})
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(AviSynth
|
||||
FOUND_VAR AviSynth_FOUND
|
||||
REQUIRED_VARS
|
||||
AviSynth_LIBRARIES
|
||||
AviSynth_INCLUDE_DIRS
|
||||
VERSION_VAR AviSynth_VERSION
|
||||
)
|
19
cmake/FindFFMS2.cmake
Normal file
19
cmake/FindFFMS2.cmake
Normal file
|
@ -0,0 +1,19 @@
|
|||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_FFMS2 QUIET ffms2)
|
||||
find_path(FFMS2_INCLUDE_DIRS
|
||||
NAMES ffms.h ffmscompat.h
|
||||
HINTS ${PC_FFMS2_INCLUDE_DIRS}
|
||||
)
|
||||
find_library(FFMS2_LIBRARIES
|
||||
NAMES ffms2
|
||||
HINTS ${PC_FFMS2_LIBRARY_DIRS}
|
||||
)
|
||||
set(FFMS2_VERSION ${PC_FFMS2_VERSION})
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(FFMS2
|
||||
FOUND_VAR FFMS2_FOUND
|
||||
REQUIRED_VARS
|
||||
FFMS2_LIBRARIES
|
||||
FFMS2_INCLUDE_DIRS
|
||||
VERSION_VAR FFMS2_VERSION
|
||||
)
|
119
cmake/FindFFTW.cmake
Normal file
119
cmake/FindFFTW.cmake
Normal file
|
@ -0,0 +1,119 @@
|
|||
# - Find the FFTW library
|
||||
#
|
||||
# Usage:
|
||||
# find_package(FFTW [REQUIRED] [QUIET] )
|
||||
#
|
||||
# It sets the following variables:
|
||||
# FFTW_FOUND ... true if fftw is found on the system
|
||||
# FFTW_LIBRARIES ... full path to fftw library
|
||||
# FFTW_INCLUDES ... fftw include directory
|
||||
#
|
||||
# The following variables will be checked by the function
|
||||
# FFTW_USE_STATIC_LIBS ... if true, only static libraries are found
|
||||
# FFTW_ROOT ... if set, the libraries are exclusively searched
|
||||
# under this path
|
||||
# FFTW_LIBRARY ... fftw library to use
|
||||
# FFTW_INCLUDE_DIR ... fftw include directory
|
||||
#
|
||||
|
||||
#If environment variable FFTWDIR is specified, it has same effect as FFTW_ROOT
|
||||
if( NOT FFTW_ROOT AND ENV{FFTWDIR} )
|
||||
set( FFTW_ROOT $ENV{FFTWDIR} )
|
||||
endif()
|
||||
|
||||
# Check if we can use PkgConfig
|
||||
find_package(PkgConfig QUIET)
|
||||
|
||||
#Determine from PKG
|
||||
if( PKG_CONFIG_FOUND AND NOT FFTW_ROOT )
|
||||
pkg_check_modules( PKG_FFTW QUIET "fftw3" )
|
||||
endif()
|
||||
|
||||
#Check whether to search static or dynamic libs
|
||||
set( CMAKE_FIND_LIBRARY_SUFFIXES_SAV ${CMAKE_FIND_LIBRARY_SUFFIXES} )
|
||||
|
||||
if( ${FFTW_USE_STATIC_LIBS} )
|
||||
set( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX} )
|
||||
else()
|
||||
set( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_SHARED_LIBRARY_SUFFIX} )
|
||||
endif()
|
||||
|
||||
if( FFTW_ROOT )
|
||||
|
||||
#find libs
|
||||
find_library(
|
||||
FFTW_LIB
|
||||
NAMES "fftw3"
|
||||
PATHS ${FFTW_ROOT}
|
||||
PATH_SUFFIXES "lib" "lib64"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
|
||||
find_library(
|
||||
FFTWF_LIB
|
||||
NAMES "fftw3f"
|
||||
PATHS ${FFTW_ROOT}
|
||||
PATH_SUFFIXES "lib" "lib64"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
|
||||
find_library(
|
||||
FFTWL_LIB
|
||||
NAMES "fftw3l"
|
||||
PATHS ${FFTW_ROOT}
|
||||
PATH_SUFFIXES "lib" "lib64"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
|
||||
#find includes
|
||||
find_path(
|
||||
FFTW_INCLUDES
|
||||
NAMES "fftw3.h"
|
||||
PATHS ${FFTW_ROOT}
|
||||
PATH_SUFFIXES "include"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
|
||||
else()
|
||||
|
||||
find_library(
|
||||
FFTW_LIB
|
||||
NAMES "fftw3"
|
||||
PATHS ${PKG_FFTW_LIBRARY_DIRS} ${LIB_INSTALL_DIR}
|
||||
)
|
||||
|
||||
find_library(
|
||||
FFTWF_LIB
|
||||
NAMES "fftw3f"
|
||||
PATHS ${PKG_FFTW_LIBRARY_DIRS} ${LIB_INSTALL_DIR}
|
||||
)
|
||||
|
||||
|
||||
find_library(
|
||||
FFTWL_LIB
|
||||
NAMES "fftw3l"
|
||||
PATHS ${PKG_FFTW_LIBRARY_DIRS} ${LIB_INSTALL_DIR}
|
||||
)
|
||||
|
||||
find_path(
|
||||
FFTW_INCLUDES
|
||||
NAMES "fftw3.h"
|
||||
PATHS ${PKG_FFTW_INCLUDE_DIRS} ${INCLUDE_INSTALL_DIR}
|
||||
)
|
||||
|
||||
endif( FFTW_ROOT )
|
||||
|
||||
set(FFTW_LIBRARIES ${FFTW_LIB} ${FFTWF_LIB})
|
||||
|
||||
if(FFTWL_LIB)
|
||||
set(FFTW_LIBRARIES ${FFTW_LIBRARIES} ${FFTWL_LIB})
|
||||
endif()
|
||||
|
||||
set( CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_SAV} )
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(FFTW DEFAULT_MSG
|
||||
FFTW_INCLUDES FFTW_LIBRARIES)
|
||||
|
||||
mark_as_advanced(FFTW_INCLUDES FFTW_LIBRARIES FFTW_LIB FFTWF_LIB FFTWL_LIB)
|
||||
|
64
cmake/FindHunspell.cmake
Normal file
64
cmake/FindHunspell.cmake
Normal file
|
@ -0,0 +1,64 @@
|
|||
# - Try to find Hunspell
|
||||
# Once done this will define
|
||||
#
|
||||
# HUNSPELL_FOUND - system has Hunspell
|
||||
# HUNSPELL_INCLUDE_DIR - the Hunspell include directory
|
||||
# HUNSPELL_LIBRARIES - Link these to use Hunspell
|
||||
# HUNSPELL_HAS_STRING_API - Hunspell has vector<string> api (>=1.5.1)
|
||||
#
|
||||
# Redistribution and use of this file is allowed according to the terms of the
|
||||
# MIT license. For details see the file COPYING-CMAKE-MODULES.
|
||||
|
||||
|
||||
# use pkg-config to get the directories and then use these values
|
||||
# in the FIND_PATH() and FIND_LIBRARY() calls
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
|
||||
pkg_check_modules(HUNSPELL_PKG QUIET hunspell)
|
||||
|
||||
FIND_PATH(HUNSPELL_INCLUDE_DIR NAMES hunspell.h
|
||||
PATHS
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
HINTS
|
||||
${HUNSPELL_PKG_INCLUDE_DIRS} # Generated by pkg-config
|
||||
PATH_SUFFIXES hunspell
|
||||
)
|
||||
|
||||
FIND_LIBRARY(HUNSPELL_LIBRARIES NAMES hunspell-1.7 hunspell-1.6 hunspell-1.5 hunspell-1.4 hunspell-1.3 hunspell-1.2 hunspell ${HUNSPELL_PKG_LIBRARIES}
|
||||
PATHS
|
||||
/usr/local
|
||||
/usr
|
||||
HINTS
|
||||
${HUNSPELL_PKG_LIBRARY_DIRS} # Generated by pkg-config
|
||||
PATH_SUFFIXES
|
||||
lib64
|
||||
lib
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Hunspell DEFAULT_MSG HUNSPELL_LIBRARIES HUNSPELL_INCLUDE_DIR )
|
||||
|
||||
add_library(hunspell UNKNOWN IMPORTED)
|
||||
set_target_properties(hunspell PROPERTIES IMPORTED_LOCATION ${HUNSPELL_LIBRARIES} INTERFACE_INCLUDE_DIRECTORIES ${HUNSPELL_INCLUDE_DIR})
|
||||
if (NOT BUILD_SHARED_LIBS)
|
||||
# At least statically compiled hunspell 1.7.0 requires HUNSPELL_STATIC
|
||||
# For other versions, it should not hurt
|
||||
set_target_properties(hunspell PROPERTIES INTERFACE_COMPILE_DEFINITIONS HUNSPELL_STATIC)
|
||||
endif ()
|
||||
|
||||
if (HUNSPELL_FOUND)
|
||||
try_compile(HUNSPELL_HAS_STRING_API "${CMAKE_BINARY_DIR}/hunspell_string_api"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/hunspell_string_api.cpp"
|
||||
LINK_LIBRARIES ${HUNSPELL_LIBRARIES}
|
||||
CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${HUNSPELL_INCLUDE_DIR}")
|
||||
if (HUNSPELL_HAS_STRING_API)
|
||||
message(STATUS "Hunspell has string API")
|
||||
else(HUNSPELL_HAS_STRING_API)
|
||||
message(STATUS "Hunspell does not have string API")
|
||||
endif(HUNSPELL_HAS_STRING_API)
|
||||
endif(HUNSPELL_FOUND)
|
||||
|
||||
# show the HUNSPELL_INCLUDE_DIR and HUNSPELL_LIBRARIES variables only in the advanced view
|
||||
MARK_AS_ADVANCED(HUNSPELL_INCLUDE_DIR HUNSPELL_LIBRARIES HUNSPELL_HAS_STRING_API)
|
14
cmake/FindOSS.cmake
Normal file
14
cmake/FindOSS.cmake
Normal file
|
@ -0,0 +1,14 @@
|
|||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_oss QUIET oss)
|
||||
find_path(OSS_INCLUDE_DIRS
|
||||
NAMES sys/soundcard.h
|
||||
HINTS ${PC_oss_INCLUDE_DIRS}
|
||||
)
|
||||
set(OSS_VERSION ${PC_ass_VERSION})
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(OSS
|
||||
FOUND_VAR OSS_FOUND
|
||||
REQUIRED_VARS
|
||||
OSS_INCLUDE_DIRS
|
||||
VERSION_VAR OSS_VERSION
|
||||
)
|
19
cmake/FindPortAudio.cmake
Normal file
19
cmake/FindPortAudio.cmake
Normal file
|
@ -0,0 +1,19 @@
|
|||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_portaudio QUIET portaudio-2.0)
|
||||
find_path(PortAudio_INCLUDE_DIRS
|
||||
NAMES portaudio.h
|
||||
HINTS ${PC_portaudio_INCLUDE_DIRS}
|
||||
)
|
||||
find_library(PortAudio_LIBRARIES
|
||||
NAMES portaudio
|
||||
HINTS ${PC_portaudio_LIBRARY_DIRS}
|
||||
)
|
||||
set(PortAudio_VERSION ${PC_portaudio_VERSION})
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(PortAudio
|
||||
FOUND_VAR PortAudio_FOUND
|
||||
REQUIRED_VARS
|
||||
PortAudio_LIBRARIES
|
||||
PortAudio_INCLUDE_DIRS
|
||||
VERSION_VAR PortAudio_VERSION
|
||||
)
|
70
cmake/FindPulseAudio.cmake
Normal file
70
cmake/FindPulseAudio.cmake
Normal file
|
@ -0,0 +1,70 @@
|
|||
# Try to find the PulseAudio library
|
||||
#
|
||||
# Once done this will define:
|
||||
#
|
||||
# PULSEAUDIO_FOUND - system has the PulseAudio library
|
||||
# PULSEAUDIO_INCLUDE_DIR - the PulseAudio include directory
|
||||
# PULSEAUDIO_LIBRARY - the libraries needed to use PulseAudio
|
||||
# PULSEAUDIO_MAINLOOP_LIBRARY - the libraries needed to use PulsAudio Mainloop
|
||||
#
|
||||
# The minimum required version of PulseAudio can be specified using the
|
||||
# standard syntax, e.g. find_package(PulseAudio 1.0)
|
||||
|
||||
# Copyright (c) 2008, Matthias Kretz, <kretz@kde.org>
|
||||
# Copyright (c) 2009, Marcus Hufgard, <Marcus.Hufgard@hufgard.de>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
# Support PULSEAUDIO_MINIMUM_VERSION for compatibility:
|
||||
if(NOT PulseAudio_FIND_VERSION)
|
||||
set(PulseAudio_FIND_VERSION "${PULSEAUDIO_MINIMUM_VERSION}")
|
||||
endif(NOT PulseAudio_FIND_VERSION)
|
||||
|
||||
# the minimum version of PulseAudio we require
|
||||
if(NOT PulseAudio_FIND_VERSION)
|
||||
set(PulseAudio_FIND_VERSION "0.9.9")
|
||||
endif(NOT PulseAudio_FIND_VERSION)
|
||||
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_PULSEAUDIO QUIET libpulse>=${PulseAudio_FIND_VERSION})
|
||||
pkg_check_modules(PC_PULSEAUDIO_MAINLOOP QUIET libpulse-mainloop-glib)
|
||||
|
||||
find_path(PULSEAUDIO_INCLUDE_DIR pulse/pulseaudio.h
|
||||
HINTS
|
||||
${PC_PULSEAUDIO_INCLUDEDIR}
|
||||
${PC_PULSEAUDIO_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
find_library(PULSEAUDIO_LIBRARY NAMES pulse libpulse
|
||||
HINTS
|
||||
${PC_PULSEAUDIO_LIBDIR}
|
||||
${PC_PULSEAUDIO_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
find_library(PULSEAUDIO_MAINLOOP_LIBRARY NAMES pulse-mainloop pulse-mainloop-glib libpulse-mainloop-glib
|
||||
HINTS
|
||||
${PC_PULSEAUDIO_LIBDIR}
|
||||
${PC_PULSEAUDIO_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
# Store the version number in the cache, so we don't have to search every time again:
|
||||
if (PULSEAUDIO_INCLUDE_DIR AND NOT PULSEAUDIO_VERSION)
|
||||
|
||||
# get PulseAudio's version from its version.h, and compare it with our minimum version
|
||||
file(STRINGS "${PULSEAUDIO_INCLUDE_DIR}/pulse/version.h" pulse_version_h
|
||||
REGEX ".*pa_get_headers_version\\(\\).*"
|
||||
)
|
||||
string(REGEX REPLACE ".*pa_get_headers_version\\(\\)\ \\(\"([0-9]+\\.[0-9]+\\.[0-9]+)[^\"]*\"\\).*" "\\1"
|
||||
_PULSEAUDIO_VERSION "${pulse_version_h}")
|
||||
|
||||
set(PULSEAUDIO_VERSION "${_PULSEAUDIO_VERSION}" CACHE STRING "Version number of PulseAudio" FORCE)
|
||||
endif (PULSEAUDIO_INCLUDE_DIR AND NOT PULSEAUDIO_VERSION)
|
||||
|
||||
# Use the new extended syntax of find_package_handle_standard_args(), which also handles version checking:
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(PulseAudio REQUIRED_VARS PULSEAUDIO_LIBRARY PULSEAUDIO_INCLUDE_DIR
|
||||
VERSION_VAR PULSEAUDIO_VERSION )
|
||||
|
||||
mark_as_advanced(PULSEAUDIO_INCLUDE_DIR PULSEAUDIO_LIBRARY PULSEAUDIO_MAINLOOP_LIBRARY)
|
20
cmake/Findass.cmake
Normal file
20
cmake/Findass.cmake
Normal file
|
@ -0,0 +1,20 @@
|
|||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_ass QUIET libass)
|
||||
find_path(ass_INCLUDE_DIRS
|
||||
NAMES ass/ass.h ass/ass_types.h
|
||||
PATH_SUFFIXES libass
|
||||
HINTS ${PC_ass_INCLUDE_DIRS}
|
||||
)
|
||||
find_library(ass_LIBRARIES
|
||||
NAMES ass
|
||||
HINTS ${PC_ass_LIBRARY_DIRS}
|
||||
)
|
||||
set(ass_VERSION ${PC_ass_VERSION})
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(ass
|
||||
FOUND_VAR ass_FOUND
|
||||
REQUIRED_VARS
|
||||
ass_LIBRARIES
|
||||
ass_INCLUDE_DIRS
|
||||
VERSION_VAR ass_VERSION
|
||||
)
|
20
cmake/Finduchardet.cmake
Normal file
20
cmake/Finduchardet.cmake
Normal file
|
@ -0,0 +1,20 @@
|
|||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_uchardet QUIET uchardet)
|
||||
find_path(uchardet_INCLUDE_DIRS
|
||||
NAMES uchardet/uchardet.h
|
||||
HINTS ${PC_uchardet_INCLUDE_DIRS}
|
||||
)
|
||||
find_library(uchardet_LIBRARIES
|
||||
NAMES uchardet
|
||||
PATH_SUFFIXES build/src
|
||||
HINTS ${PC_uchardet_LIBRARY_DIRS}
|
||||
)
|
||||
set(uchardet_VERSION ${PC_uchardet_VERSION})
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(uchardet
|
||||
FOUND_VAR uchardet_FOUND
|
||||
REQUIRED_VARS
|
||||
uchardet_LIBRARIES
|
||||
uchardet_INCLUDE_DIRS
|
||||
VERSION_VAR uchardet_VERSION
|
||||
)
|
10
cmake/hunspell_string_api.cpp
Normal file
10
cmake/hunspell_string_api.cpp
Normal file
|
@ -0,0 +1,10 @@
|
|||
#include <hunspell/hunspell.hxx>
|
||||
#include <string>
|
||||
|
||||
int main() {
|
||||
Hunspell hunspell(NULL, NULL);
|
||||
std::string word = "";
|
||||
hunspell.suggest(word);
|
||||
hunspell.spell(word);
|
||||
return 0;
|
||||
}
|
27
configure.ac
27
configure.ac
|
@ -54,6 +54,13 @@ AEGISUB_CATALOG="aegisub"
|
|||
AC_SUBST(AEGISUB_CATALOG)
|
||||
AC_DEFINE_UNQUOTED([AEGISUB_CATALOG], ["${AEGISUB_CATALOG}"], [Name of the Aegisub gettext catalog])
|
||||
|
||||
# Handle location of appdata files: https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html#spec-component-location
|
||||
AC_ARG_WITH(appdata-dir,
|
||||
AS_HELP_STRING([--with-appdata-dir=PATH],[appdata file locations [PREFIX/share/metainfo]]))
|
||||
|
||||
P_APPDATA=${with_appdata_dir:-$datarootdir/metainfo}
|
||||
AC_SUBST(P_APPDATA)
|
||||
|
||||
# Handle location of desktop files: http://freedesktop.org/wiki/Specifications/desktop-entry-spec
|
||||
AC_ARG_WITH(desktop-dir,
|
||||
AS_HELP_STRING([--with-desktop-dir=PATH],[desktop file locations [PREFIX/share/applications]]))
|
||||
|
@ -133,20 +140,18 @@ AS_IF([test x$build_darwin != xyes], [
|
|||
AC_ARG_ENABLE(compiler-flags, AS_HELP_STRING([--disable-compiler-flags],[Disable *all* additional compiler flags. [no]]))
|
||||
|
||||
AS_IF([test x$enable_compiler_flags != xno], [
|
||||
CFLAGS="$CFLAGS -Wall -Wextra -Wno-unused-parameter -std=gnu99 -pipe -g"
|
||||
CXXFLAGS="$CXXFLAGS -Wall -Wextra -Wno-unused-parameter -fno-strict-aliasing -pipe -g"
|
||||
CFLAGS="$CFLAGS -Wall -Wextra -Wno-unused-parameter"
|
||||
CXXFLAGS="$CXXFLAGS -Wall -Wextra -Wno-unused-parameter -fno-strict-aliasing"
|
||||
AC_C_FLAG([-std=gnu99])
|
||||
AC_CXX_FLAG([-std=c++11])
|
||||
AC_CXX_FLAG([-Wno-c++11-narrowing])
|
||||
AC_CXX_FLAG([-Wno-narrowing])
|
||||
AC_C_FLAG([-Wno-unused-local-typedefs])
|
||||
AC_CXX_FLAG([-Wno-unused-local-typedefs])
|
||||
|
||||
# -O* messes with debugging.
|
||||
AS_IF([test x$enable_debug = xyes], [
|
||||
CFLAGS="$CFLAGS -O0"
|
||||
CXXFLAGS="$CXXFLAGS -O0"
|
||||
], [
|
||||
CFLAGS="$CFLAGS -O3"
|
||||
CXXFLAGS="$CXXFLAGS -O3"
|
||||
CFLAGS="$CFLAGS -O0 -g"
|
||||
CXXFLAGS="$CXXFLAGS -O0 -g"
|
||||
])
|
||||
])
|
||||
|
||||
|
@ -334,9 +339,9 @@ AS_IF([test x$with_openal != xno], [
|
|||
#endif
|
||||
int main(void) {
|
||||
ALCdevice *device = alcOpenDevice(0);
|
||||
if (!device) return 1;
|
||||
ALCcontext *context = alcCreateContext(device, 0);
|
||||
if (!context) return 1;
|
||||
alcDestroyContext(context);
|
||||
alcCloseDevice(device);
|
||||
return 0;
|
||||
} ])
|
||||
])
|
||||
|
@ -505,6 +510,7 @@ AC_PCH_FLAG([-fpch-preprocess])
|
|||
# Internationalisation support
|
||||
##############################
|
||||
AM_GNU_GETTEXT([external])
|
||||
AM_GNU_GETTEXT_VERSION([0.19.7])
|
||||
|
||||
################
|
||||
# Update checker
|
||||
|
@ -564,6 +570,7 @@ DEFAULT_PLAYER_AUDIO=${DEFAULT_PLAYER_AUDIO:-NONE}
|
|||
# Files that need substitution.
|
||||
AC_CONFIG_FILES([
|
||||
packages/desktop/aegisub.desktop.template
|
||||
packages/desktop/aegisub.appdata.xml.template
|
||||
src/libresrc/default_config_platform.json
|
||||
tools/osx-bundle.sed
|
||||
Makefile.inc
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include <algorithm>
|
||||
|
||||
namespace agi {
|
||||
Time::Time(int time) : time(util::mid(0, time, 10 * 60 * 60 * 1000 - 1)) { }
|
||||
Time::Time(int time) : time(util::mid(0, time, 10 * 60 * 60 * 1000 - 6)) { }
|
||||
|
||||
Time::Time(std::string const& text) {
|
||||
int after_decimal = -1;
|
||||
|
@ -56,29 +56,39 @@ Time::Time(std::string const& text) {
|
|||
time = (time * 60 + current) * 1000;
|
||||
|
||||
// Limit to the valid range
|
||||
time = util::mid(0, time, 10 * 60 * 60 * 1000 - 1);
|
||||
time = util::mid(0, time, 10 * 60 * 60 * 1000 - 6);
|
||||
}
|
||||
|
||||
std::string Time::GetAssFormatted(bool msPrecision) const {
|
||||
int ass_time = msPrecision ? time : int(*this);
|
||||
std::string ret(10 + msPrecision, ':');
|
||||
ret[0] = '0' + GetTimeHours();
|
||||
ret[2] = '0' + (time % (60 * 60 * 1000)) / (60 * 1000 * 10);
|
||||
ret[3] = '0' + (time % (10 * 60 * 1000)) / (60 * 1000);
|
||||
ret[5] = '0' + (time % (60 * 1000)) / (1000 * 10);
|
||||
ret[6] = '0' + (time % (10 * 1000)) / 1000;
|
||||
ret[0] = '0' + ass_time / 3600000;
|
||||
ret[2] = '0' + (ass_time % (60 * 60 * 1000)) / (60 * 1000 * 10);
|
||||
ret[3] = '0' + (ass_time % (10 * 60 * 1000)) / (60 * 1000);
|
||||
ret[5] = '0' + (ass_time % (60 * 1000)) / (1000 * 10);
|
||||
ret[6] = '0' + (ass_time % (10 * 1000)) / 1000;
|
||||
ret[7] = '.';
|
||||
ret[8] = '0' + (time % 1000) / 100;
|
||||
ret[9] = '0' + (time % 100) / 10;
|
||||
ret[8] = '0' + (ass_time % 1000) / 100;
|
||||
ret[9] = '0' + (ass_time % 100) / 10;
|
||||
if (msPrecision)
|
||||
ret[10] = '0' + time % 10;
|
||||
ret[10] = '0' + ass_time % 10;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Time::GetTimeHours() const { return time / 3600000; }
|
||||
int Time::GetTimeMinutes() const { return (time % 3600000) / 60000; }
|
||||
int Time::GetTimeSeconds() const { return (time % 60000) / 1000; }
|
||||
int Time::GetTimeMiliseconds() const { return (time % 1000); }
|
||||
int Time::GetTimeCentiseconds() const { return (time % 1000) / 10; }
|
||||
std::string Time::GetSrtFormatted() const {
|
||||
std::string ret(12, ':');
|
||||
ret[0] = '0';
|
||||
ret[1] = '0' + time / 3600000;
|
||||
ret[3] = '0' + (time % (60 * 60 * 1000)) / (60 * 1000 * 10);
|
||||
ret[4] = '0' + (time % (10 * 60 * 1000)) / (60 * 1000);
|
||||
ret[6] = '0' + (time % (60 * 1000)) / (1000 * 10);
|
||||
ret[7] = '0' + (time % (10 * 1000)) / 1000;
|
||||
ret[8] = ',';
|
||||
ret[9] = '0' + (time % 1000) / 100;
|
||||
ret[10] = '0' + (time % 100) / 10;
|
||||
ret[11] = '0' + time % 10;
|
||||
return ret;
|
||||
}
|
||||
|
||||
SmpteFormatter::SmpteFormatter(vfr::Framerate fps, char sep)
|
||||
: fps(std::move(fps))
|
||||
|
|
|
@ -21,13 +21,107 @@
|
|||
#include "libaegisub/log.h"
|
||||
#include "libaegisub/util.h"
|
||||
|
||||
namespace agi {
|
||||
void AudioProvider::GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const {
|
||||
GetAudio(buf, start, count);
|
||||
namespace {
|
||||
|
||||
template<typename Source>
|
||||
class ConvertFloatToInt16 {
|
||||
Source* src;
|
||||
public:
|
||||
ConvertFloatToInt16(Source* src) :src(src) {}
|
||||
int16_t operator[](size_t idx) const {
|
||||
Source expanded = src[idx] * 32768;
|
||||
return expanded < -32768 ? -32768 :
|
||||
expanded > 32767 ? 32767 :
|
||||
static_cast<int16_t>(expanded);
|
||||
}
|
||||
};
|
||||
|
||||
// 8 bits per sample is assumed to be unsigned with a bias of 128,
|
||||
// while everything else is assumed to be signed with zero bias
|
||||
class ConvertIntToInt16 {
|
||||
void* src;
|
||||
int bytes_per_sample;
|
||||
public:
|
||||
ConvertIntToInt16(void* src, int bytes_per_sample) :src(src), bytes_per_sample(bytes_per_sample) {}
|
||||
const int16_t& operator[](size_t idx) const {
|
||||
return *reinterpret_cast<int16_t*>(reinterpret_cast<char*>(src) + (idx + 1) * bytes_per_sample - sizeof(int16_t));
|
||||
}
|
||||
};
|
||||
class ConvertUInt8ToInt16 {
|
||||
uint8_t* src;
|
||||
public:
|
||||
ConvertUInt8ToInt16(uint8_t* src) :src(src) {}
|
||||
int16_t operator[](size_t idx) const {
|
||||
return int16_t(src[idx]-128) << 8;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Source>
|
||||
class DownmixToMono {
|
||||
Source src;
|
||||
int channels;
|
||||
public:
|
||||
DownmixToMono(Source src, int channels) :src(src), channels(channels) {}
|
||||
int16_t operator[](size_t idx) const {
|
||||
int ret = 0;
|
||||
// Just average the channels together
|
||||
for (int i = 0; i < channels; ++i)
|
||||
ret += src[idx * channels + i];
|
||||
return ret / channels;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace agi {
|
||||
void AudioProvider::FillBufferInt16Mono(int16_t* buf, int64_t start, int64_t count) const {
|
||||
if (!float_samples && bytes_per_sample == 2 && channels == 1) {
|
||||
FillBuffer(buf, start, count);
|
||||
return;
|
||||
}
|
||||
void* buff = malloc(bytes_per_sample * count * channels);
|
||||
FillBuffer(buff, start, count);
|
||||
if (channels == 1) {
|
||||
if (float_samples) {
|
||||
if (bytes_per_sample == sizeof(float))
|
||||
for (int64_t i = 0; i < count; ++i)
|
||||
buf[i] = ConvertFloatToInt16<float>(reinterpret_cast<float*>(buff))[i];
|
||||
else if (bytes_per_sample == sizeof(double))
|
||||
for (int64_t i = 0; i < count; ++i)
|
||||
buf[i] = ConvertFloatToInt16<double>(reinterpret_cast<double*>(buff))[i];
|
||||
}
|
||||
else {
|
||||
if (bytes_per_sample == sizeof(uint8_t))
|
||||
for (int64_t i = 0; i < count; ++i)
|
||||
buf[i] = ConvertUInt8ToInt16(reinterpret_cast<uint8_t*>(buff))[i];
|
||||
else
|
||||
for (int64_t i = 0; i < count; ++i)
|
||||
buf[i] = ConvertIntToInt16(buff, bytes_per_sample)[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (float_samples) {
|
||||
if (bytes_per_sample == sizeof(float))
|
||||
for (int64_t i = 0; i < count; ++i)
|
||||
buf[i] = DownmixToMono<ConvertFloatToInt16<float> >(ConvertFloatToInt16<float>(reinterpret_cast<float*>(buff)), channels)[i];
|
||||
else if (bytes_per_sample == sizeof(double))
|
||||
for (int64_t i = 0; i < count; ++i)
|
||||
buf[i] = DownmixToMono<ConvertFloatToInt16<double> >(ConvertFloatToInt16<double>(reinterpret_cast<double*>(buff)), channels)[i];
|
||||
}
|
||||
else {
|
||||
if (bytes_per_sample == sizeof(uint8_t))
|
||||
for (int64_t i = 0; i < count; ++i)
|
||||
buf[i] = DownmixToMono<ConvertUInt8ToInt16>(ConvertUInt8ToInt16(reinterpret_cast<uint8_t*>(buff)), channels)[i];
|
||||
else
|
||||
for (int64_t i = 0; i < count; ++i)
|
||||
buf[i] = DownmixToMono<ConvertIntToInt16>(ConvertIntToInt16(buff, bytes_per_sample), channels)[i];
|
||||
}
|
||||
}
|
||||
free(buff);
|
||||
}
|
||||
|
||||
void AudioProvider::GetInt16MonoAudioWithVolume(int16_t *buf, int64_t start, int64_t count, double volume) const {
|
||||
GetInt16MonoAudio(buf, start, count);
|
||||
if (volume == 1.0) return;
|
||||
if (bytes_per_sample != 2)
|
||||
throw agi::InternalError("GetAudioWithVolume called on unconverted audio stream");
|
||||
|
||||
auto buffer = static_cast<int16_t *>(buf);
|
||||
for (size_t i = 0; i < (size_t)count; ++i)
|
||||
|
@ -75,6 +169,39 @@ void AudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
|
|||
}
|
||||
}
|
||||
|
||||
void AudioProvider::GetInt16MonoAudio(int16_t* buf, int64_t start, int64_t count) const {
|
||||
if (start < 0) {
|
||||
memset(buf, 0, sizeof(int16_t) * std::min(-start, count));
|
||||
buf -= start;
|
||||
count += start;
|
||||
start = 0;
|
||||
}
|
||||
|
||||
if (start + count > num_samples) {
|
||||
int64_t zero_count = std::min(count, start + count - num_samples);
|
||||
count -= zero_count;
|
||||
memset(buf + count, 0, sizeof(int16_t) * zero_count);
|
||||
}
|
||||
|
||||
if (count <= 0) return;
|
||||
|
||||
try {
|
||||
FillBufferInt16Mono(buf, start, count);
|
||||
}
|
||||
catch (AudioDecodeError const& e) {
|
||||
// We don't have any good way to report errors here, so just log the
|
||||
// failure and return silence
|
||||
LOG_E("audio_provider") << e.GetMessage();
|
||||
memset(buf, 0, sizeof(int16_t) * count);
|
||||
return;
|
||||
}
|
||||
catch (...) {
|
||||
LOG_E("audio_provider") << "Unknown audio decoding error";
|
||||
memset(buf, 0, sizeof(int16_t) * count);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
class writer {
|
||||
io::Save outfile;
|
||||
|
|
|
@ -23,115 +23,18 @@
|
|||
|
||||
using namespace agi;
|
||||
|
||||
/// Anything integral -> 16 bit signed machine-endian audio converter
|
||||
/// Anything -> mono 16 bit signed machine-endian audio converter
|
||||
namespace {
|
||||
template<class Target>
|
||||
class BitdepthConvertAudioProvider final : public AudioProviderWrapper {
|
||||
int src_bytes_per_sample;
|
||||
mutable std::vector<uint8_t> src_buf;
|
||||
|
||||
class ConvertAudioProvider final : public AudioProviderWrapper {
|
||||
public:
|
||||
BitdepthConvertAudioProvider(std::unique_ptr<AudioProvider> src) : AudioProviderWrapper(std::move(src)) {
|
||||
if (bytes_per_sample > 8)
|
||||
throw AudioProviderError("Audio format converter: audio with bitdepths greater than 64 bits/sample is currently unsupported");
|
||||
|
||||
src_bytes_per_sample = bytes_per_sample;
|
||||
bytes_per_sample = sizeof(Target);
|
||||
}
|
||||
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count64) const override {
|
||||
auto count = static_cast<size_t>(count64);
|
||||
assert(count == count64);
|
||||
|
||||
src_buf.resize(count * src_bytes_per_sample * channels);
|
||||
source->GetAudio(src_buf.data(), start, count);
|
||||
|
||||
auto dest = static_cast<int16_t*>(buf);
|
||||
|
||||
for (int64_t i = 0; i < count * channels; ++i) {
|
||||
int64_t sample = 0;
|
||||
|
||||
// 8 bits per sample is assumed to be unsigned with a bias of 127,
|
||||
// while everything else is assumed to be signed with zero bias
|
||||
if (src_bytes_per_sample == 1)
|
||||
sample = src_buf[i] - 128;
|
||||
else {
|
||||
for (int j = src_bytes_per_sample; j > 0; --j) {
|
||||
sample <<= 8;
|
||||
sample += src_buf[i * src_bytes_per_sample + j - 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (static_cast<size_t>(src_bytes_per_sample) > sizeof(Target))
|
||||
sample /= 1LL << (src_bytes_per_sample - sizeof(Target)) * 8;
|
||||
else if (static_cast<size_t>(src_bytes_per_sample) < sizeof(Target))
|
||||
sample *= 1LL << (sizeof(Target) - src_bytes_per_sample ) * 8;
|
||||
|
||||
dest[i] = static_cast<Target>(sample);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Floating point -> 16 bit signed machine-endian audio converter
|
||||
template<class Source, class Target>
|
||||
class FloatConvertAudioProvider final : public AudioProviderWrapper {
|
||||
mutable std::vector<Source> src_buf;
|
||||
|
||||
public:
|
||||
FloatConvertAudioProvider(std::unique_ptr<AudioProvider> src) : AudioProviderWrapper(std::move(src)) {
|
||||
bytes_per_sample = sizeof(Target);
|
||||
ConvertAudioProvider(std::unique_ptr<AudioProvider> src) : AudioProviderWrapper(std::move(src)) {
|
||||
float_samples = false;
|
||||
}
|
||||
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count64) const override {
|
||||
auto count = static_cast<size_t>(count64);
|
||||
assert(count == count64);
|
||||
|
||||
src_buf.resize(count * channels);
|
||||
source->GetAudio(&src_buf[0], start, count);
|
||||
|
||||
auto dest = static_cast<Target*>(buf);
|
||||
|
||||
for (size_t i = 0; i < static_cast<size_t>(count * channels); ++i) {
|
||||
Source expanded;
|
||||
if (src_buf[i] < 0)
|
||||
expanded = static_cast<Target>(-src_buf[i] * std::numeric_limits<Target>::min());
|
||||
else
|
||||
expanded = static_cast<Target>(src_buf[i] * std::numeric_limits<Target>::max());
|
||||
|
||||
dest[i] = expanded < std::numeric_limits<Target>::min() ? std::numeric_limits<Target>::min() :
|
||||
expanded > std::numeric_limits<Target>::max() ? std::numeric_limits<Target>::max() :
|
||||
static_cast<Target>(expanded);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Non-mono 16-bit signed machine-endian -> mono 16-bit signed machine endian converter
|
||||
class DownmixAudioProvider final : public AudioProviderWrapper {
|
||||
int src_channels;
|
||||
mutable std::vector<int16_t> src_buf;
|
||||
|
||||
public:
|
||||
DownmixAudioProvider(std::unique_ptr<AudioProvider> src) : AudioProviderWrapper(std::move(src)) {
|
||||
src_channels = channels;
|
||||
channels = 1;
|
||||
bytes_per_sample = sizeof(int16_t);
|
||||
}
|
||||
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count64) const override {
|
||||
auto count = static_cast<size_t>(count64);
|
||||
assert(count == count64);
|
||||
|
||||
src_buf.resize(count * src_channels);
|
||||
source->GetAudio(&src_buf[0], start, count);
|
||||
|
||||
auto dst = static_cast<int16_t*>(buf);
|
||||
// Just average the channels together
|
||||
while (count-- > 0) {
|
||||
int sum = 0;
|
||||
for (int c = 0; c < src_channels; ++c)
|
||||
sum += src_buf[count * src_channels + c];
|
||||
dst[count] = static_cast<int16_t>(sum / src_channels);
|
||||
}
|
||||
void FillBuffer(void *buf, int64_t start, int64_t count) const override {
|
||||
source->GetInt16MonoAudio(reinterpret_cast<int16_t*>(buf), start, count);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -175,29 +78,23 @@ public:
|
|||
namespace agi {
|
||||
std::unique_ptr<AudioProvider> CreateConvertAudioProvider(std::unique_ptr<AudioProvider> provider) {
|
||||
// Ensure 16-bit audio with proper endianness
|
||||
if (provider->AreSamplesFloat()) {
|
||||
if (provider->AreSamplesFloat())
|
||||
LOG_D("audio_provider") << "Converting float to S16";
|
||||
if (provider->GetBytesPerSample() == sizeof(float))
|
||||
provider = agi::make_unique<FloatConvertAudioProvider<float, int16_t>>(std::move(provider));
|
||||
else
|
||||
provider = agi::make_unique<FloatConvertAudioProvider<double, int16_t>>(std::move(provider));
|
||||
}
|
||||
if (provider->GetBytesPerSample() != 2) {
|
||||
LOG_D("audio_provider") << "Converting " << provider->GetBytesPerSample() << " bytes per sample or wrong endian to S16";
|
||||
provider = agi::make_unique<BitdepthConvertAudioProvider<int16_t>>(std::move(provider));
|
||||
}
|
||||
else if (provider->GetBytesPerSample() != 2)
|
||||
LOG_D("audio_provider") << "Converting " << provider->GetBytesPerSample() << " bytes per sample to S16";
|
||||
|
||||
// We currently only support mono audio
|
||||
if (provider->GetChannels() != 1) {
|
||||
if (provider->GetChannels() != 1)
|
||||
LOG_D("audio_provider") << "Downmixing to mono from " << provider->GetChannels() << " channels";
|
||||
provider = agi::make_unique<DownmixAudioProvider>(std::move(provider));
|
||||
}
|
||||
|
||||
// Some players don't like low sample rate audio
|
||||
if (provider->GetSampleRate() < 32000) {
|
||||
provider = agi::make_unique<ConvertAudioProvider>(std::move(provider));
|
||||
while (provider->GetSampleRate() < 32000) {
|
||||
LOG_D("audio_provider") << "Doubling sample rate";
|
||||
provider = agi::make_unique<SampleDoublingAudioProvider>(std::move(provider));
|
||||
}
|
||||
}
|
||||
|
||||
return provider;
|
||||
}
|
||||
|
|
|
@ -38,20 +38,20 @@ class HDAudioProvider final : public AudioProviderWrapper {
|
|||
void FillBuffer(void *buf, int64_t start, int64_t count) const override {
|
||||
auto missing = std::min(count, start + count - decoded_samples);
|
||||
if (missing > 0) {
|
||||
memset(static_cast<int16_t*>(buf) + count - missing, 0, missing * bytes_per_sample);
|
||||
memset(static_cast<int16_t*>(buf) + count - missing, 0, missing * bytes_per_sample * channels);
|
||||
count -= missing;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
start *= bytes_per_sample;
|
||||
count *= bytes_per_sample;
|
||||
start *= bytes_per_sample * channels;
|
||||
count *= bytes_per_sample * channels;
|
||||
memcpy(buf, file.read(start, count), count);
|
||||
}
|
||||
}
|
||||
|
||||
fs::path CacheFilename(fs::path const& dir) {
|
||||
// Check free space
|
||||
if ((uint64_t)num_samples * bytes_per_sample > fs::FreeSpace(dir))
|
||||
if ((uint64_t)num_samples * bytes_per_sample * channels > fs::FreeSpace(dir))
|
||||
throw AudioProviderError("Not enough free disk space in " + dir.string() + " to cache the audio");
|
||||
|
||||
return format("audio-%lld-%lld", time(nullptr),
|
||||
|
@ -61,7 +61,7 @@ class HDAudioProvider final : public AudioProviderWrapper {
|
|||
public:
|
||||
HDAudioProvider(std::unique_ptr<AudioProvider> src, agi::fs::path const& dir)
|
||||
: AudioProviderWrapper(std::move(src))
|
||||
, file(dir / CacheFilename(dir), num_samples * bytes_per_sample)
|
||||
, file(dir / CacheFilename(dir), num_samples * bytes_per_sample* channels)
|
||||
{
|
||||
decoded_samples = 0;
|
||||
decoder = std::thread([&] {
|
||||
|
@ -69,7 +69,7 @@ public:
|
|||
for (int64_t i = 0; i < num_samples; i += block) {
|
||||
if (cancelled) break;
|
||||
block = std::min(block, num_samples - i);
|
||||
source->GetAudio(file.write(i * bytes_per_sample, block * bytes_per_sample), i, block);
|
||||
source->GetAudio(file.write(i * bytes_per_sample * channels, block * bytes_per_sample * channels), i, block);
|
||||
decoded_samples += block;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -29,6 +29,11 @@ class LockAudioProvider final : public agi::AudioProviderWrapper {
|
|||
source->GetAudio(buf, start, count);
|
||||
}
|
||||
|
||||
void FillBufferInt16Mono(int16_t *buf, int64_t start, int64_t count) const override {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
source->GetInt16MonoAudio(buf, start, count);
|
||||
}
|
||||
|
||||
public:
|
||||
LockAudioProvider(std::unique_ptr<AudioProvider> src)
|
||||
: AudioProviderWrapper(std::move(src))
|
||||
|
|
|
@ -56,6 +56,7 @@ class PCMAudioProvider : public AudioProvider {
|
|||
start += read_count;
|
||||
pos += ip.num_samples;
|
||||
}
|
||||
ZeroFill(write_buf, count);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -67,7 +68,7 @@ protected:
|
|||
template<typename T, typename UInt>
|
||||
T Read(UInt *data_left) {
|
||||
if (*data_left < sizeof(T)) throw file_ended();
|
||||
if (file.size() - file_pos < sizeof(T)) throw file_ended();
|
||||
if (file_pos > file.size() || file.size() - file_pos < sizeof(T)) throw file_ended();
|
||||
|
||||
auto data = file.read(file_pos, sizeof(T));
|
||||
file_pos += sizeof(T);
|
||||
|
@ -89,7 +90,7 @@ struct FourCC {
|
|||
bool operator==(const char *cmp) const { return !(*this != cmp); }
|
||||
};
|
||||
|
||||
// Overview of RIFF WAV: <http://www.sonicspot.com/guide/wavefiles.html>
|
||||
// Overview of RIFF WAV: https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653308(v=vs.85)
|
||||
struct RiffWav {
|
||||
using DataSize = uint32_t;
|
||||
using ChunkId = FourCC;
|
||||
|
@ -127,7 +128,7 @@ static const GUID w64Guiddata = {{
|
|||
0x64, 0x61, 0x74, 0x61, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A
|
||||
}};
|
||||
|
||||
// http://www.vcs.de/fileadmin/user_upload/MBS/PDF/Whitepaper/Informations_about_Sony_Wave64.pdf
|
||||
// http://www.ambisonia.com/Members/mleese/sony_wave64.pdf/sony_wave64.pdf
|
||||
struct Wave64 {
|
||||
using DataSize = uint64_t;
|
||||
using ChunkId = GUID;
|
||||
|
@ -140,7 +141,7 @@ struct Wave64 {
|
|||
static const uint64_t alignment = 7ULL;
|
||||
|
||||
// Wave 64 includes the size of the header in the chunk sizes
|
||||
static uint64_t data_size(uint64_t size) { return size - 16; }
|
||||
static uint64_t data_size(uint64_t size) { return size - 24; }
|
||||
static uint64_t chunk_size(uint64_t size) { return size - 24; }
|
||||
};
|
||||
|
||||
|
@ -164,9 +165,11 @@ public:
|
|||
throw AudioDataNotFound("File is not a RIFF WAV file");
|
||||
|
||||
while (data_left) {
|
||||
|
||||
auto chunk_fcc = Read<ChunkId>(&data_left);
|
||||
auto chunk_size = Impl::chunk_size(Read<DataSize>(&data_left));
|
||||
|
||||
uint64_t chunk_end = file_pos + chunk_size;
|
||||
data_left -= std::min(chunk_size, data_left);
|
||||
|
||||
if (chunk_fcc == Impl::fmt_id()) {
|
||||
|
@ -186,14 +189,16 @@ public:
|
|||
else if (chunk_fcc == Impl::data_id()) {
|
||||
if (!channels || !sample_rate || !bytes_per_sample)
|
||||
throw AudioProviderError("Found 'data' chunk without format being set.");
|
||||
index_points.emplace_back(IndexPoint{file_pos, chunk_size / bytes_per_sample / channels});
|
||||
num_samples += chunk_size / bytes_per_sample / channels;
|
||||
uint64_t chunk_decoded_samples = std::min<uint64_t>(chunk_size, file.size() - file_pos) / bytes_per_sample / channels;
|
||||
decoded_samples += chunk_decoded_samples;
|
||||
index_points.emplace_back(IndexPoint{file_pos, chunk_decoded_samples });
|
||||
}
|
||||
// There's a bunch of other chunk types. They're all dumb.
|
||||
|
||||
// blocks are aligned and the padding bytes are not included in
|
||||
// the size of the chunk
|
||||
file_pos += (chunk_size + Impl::alignment) & ~Impl::alignment;
|
||||
file_pos = (chunk_end + Impl::alignment) & ~Impl::alignment;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -202,7 +207,8 @@ public:
|
|||
throw AudioDataNotFound("File ended before reaching format chunk");
|
||||
// Truncated files are fine otherwise
|
||||
}
|
||||
decoded_samples = num_samples;
|
||||
if (decoded_samples == 0)
|
||||
throw AudioDataNotFound("No audio sample can be decoded");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -46,14 +46,14 @@ public:
|
|||
decoded_samples = 0;
|
||||
|
||||
try {
|
||||
blockcache.resize((source->GetNumSamples() * source->GetBytesPerSample() + CacheBlockSize - 1) >> CacheBits);
|
||||
blockcache.resize((num_samples * bytes_per_sample * channels + CacheBlockSize - 1) >> CacheBits);
|
||||
}
|
||||
catch (std::bad_alloc const&) {
|
||||
throw AudioProviderError("Not enough memory available to cache in RAM");
|
||||
}
|
||||
|
||||
decoder = std::thread([&] {
|
||||
int64_t readsize = CacheBlockSize / source->GetBytesPerSample();
|
||||
int64_t readsize = CacheBlockSize / bytes_per_sample / channels;
|
||||
for (size_t i = 0; i < blockcache.size(); i++) {
|
||||
if (cancelled) break;
|
||||
auto actual_read = std::min<int64_t>(readsize, num_samples - i * readsize);
|
||||
|
@ -71,20 +71,22 @@ public:
|
|||
|
||||
void RAMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const {
|
||||
auto charbuf = static_cast<char *>(buf);
|
||||
for (int64_t bytes_remaining = count * bytes_per_sample; bytes_remaining; ) {
|
||||
for (int64_t bytes_remaining = count * bytes_per_sample * channels; bytes_remaining; ) {
|
||||
if (start >= decoded_samples) {
|
||||
memset(charbuf, 0, bytes_remaining);
|
||||
break;
|
||||
}
|
||||
|
||||
const int i = (start * bytes_per_sample) >> CacheBits;
|
||||
const int start_offset = (start * bytes_per_sample) & (CacheBlockSize-1);
|
||||
const int read_size = std::min<int>(bytes_remaining, CacheBlockSize - start_offset);
|
||||
const int64_t samples_per_block = CacheBlockSize / bytes_per_sample / channels;
|
||||
|
||||
const size_t i = start / samples_per_block;
|
||||
const int start_offset = (start % samples_per_block) * bytes_per_sample * channels;
|
||||
const int read_size = std::min<int>(bytes_remaining, samples_per_block * bytes_per_sample * channels - start_offset);
|
||||
|
||||
memcpy(charbuf, &blockcache[i][start_offset], read_size);
|
||||
charbuf += read_size;
|
||||
bytes_remaining -= read_size;
|
||||
start += read_size / bytes_per_sample;
|
||||
start += read_size / bytes_per_sample / channels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ struct utext_deleter {
|
|||
};
|
||||
using utext_ptr = std::unique_ptr<UText, utext_deleter>;
|
||||
|
||||
UChar32 ass_special_chars[] = {'n', 'N', 'h'};
|
||||
|
||||
icu::BreakIterator& get_break_iterator(const char *ptr, size_t len) {
|
||||
static std::unique_ptr<icu::BreakIterator> bi;
|
||||
static std::once_flag token;
|
||||
|
@ -65,10 +67,25 @@ size_t count_in_range(Iterator begin, Iterator end, int mask) {
|
|||
UChar32 c;
|
||||
int i = 0;
|
||||
U8_NEXT_UNSAFE(begin + pos, i, c);
|
||||
if ((U_GET_GC_MASK(c) & mask) == 0)
|
||||
if ((U_GET_GC_MASK(c) & mask) == 0) {
|
||||
if (mask & U_GC_Z_MASK && pos != 0) {
|
||||
UChar32 *result = std::find(std::begin(ass_special_chars), std::end(ass_special_chars), c);
|
||||
if (result != std::end(ass_special_chars)) {
|
||||
UChar32 c2;
|
||||
i = 0;
|
||||
U8_PREV_UNSAFE(begin + pos, i, c2);
|
||||
if (c2 != (UChar32) '\\')
|
||||
++count;
|
||||
else if (!(mask & U_GC_P_MASK))
|
||||
--count;
|
||||
}
|
||||
else
|
||||
++count;
|
||||
}
|
||||
|
||||
else
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
|
|
@ -29,37 +29,44 @@ namespace agi { namespace charset {
|
|||
std::string Detect(agi::fs::path const& file) {
|
||||
agi::read_file_mapping fp(file);
|
||||
|
||||
// FIXME: It is an empty file. Treat as ascii
|
||||
if (fp.size() == 0) return "ascii";
|
||||
|
||||
// FIXME: Dirty hack for Matroska. These 4 bytes are the magic
|
||||
// number of EBML which is used by mkv and webm
|
||||
if (fp.size() >= 4) {
|
||||
const char* buf = fp.read(0, 4);
|
||||
if (!strncmp(buf, "\x1a\x45\xdf\xa3", 4))
|
||||
return "binary";
|
||||
}
|
||||
|
||||
#ifdef WITH_UCHARDET
|
||||
agi::scoped_holder<uchardet_t> ud(uchardet_new(), uchardet_delete);
|
||||
for (uint64_t offset = 0; offset < fp.size(); ) {
|
||||
auto read = std::min<uint64_t>(65536, fp.size() - offset);
|
||||
auto buf = fp.read(offset, read);
|
||||
uchardet_handle_data(ud, buf, read);
|
||||
offset += read;
|
||||
}
|
||||
uchardet_data_end(ud);
|
||||
std::string encoding = uchardet_get_charset(ud);
|
||||
return encoding.empty() ? "binary" : encoding;
|
||||
#else
|
||||
|
||||
// Look for utf-8 BOM
|
||||
if (fp.size() >= 3) {
|
||||
const char* buf = fp.read(0, 3);
|
||||
if (!strncmp(buf, "\xef\xbb\xbf", 3))
|
||||
return "utf-8";
|
||||
}
|
||||
|
||||
// If it's over 100 MB it's either binary or big enough that we won't
|
||||
// be able to do anything useful with it anyway
|
||||
if (fp.size() > 100 * 1024 * 1024)
|
||||
return "binary";
|
||||
|
||||
uint64_t binaryish = 0;
|
||||
|
||||
#ifdef WITH_UCHARDET
|
||||
agi::scoped_holder<uchardet_t> ud(uchardet_new(), uchardet_delete);
|
||||
for (uint64_t offset = 0; offset < fp.size(); ) {
|
||||
auto read = std::min<uint64_t>(4096, fp.size() - offset);
|
||||
auto buf = fp.read(offset, read);
|
||||
uchardet_handle_data(ud, buf, read);
|
||||
uchardet_data_end(ud);
|
||||
if (*uchardet_get_charset(ud))
|
||||
return uchardet_get_charset(ud);
|
||||
|
||||
offset += read;
|
||||
|
||||
// A dumb heuristic to detect binary files
|
||||
for (size_t i = 0; i < read; ++i) {
|
||||
if ((unsigned char)buf[i] < 32 && (buf[i] != '\r' && buf[i] != '\n' && buf[i] != '\t'))
|
||||
++binaryish;
|
||||
}
|
||||
|
||||
if (binaryish > offset / 8)
|
||||
return "binary";
|
||||
}
|
||||
return uchardet_get_charset(ud);
|
||||
#else
|
||||
auto read = std::min<uint64_t>(4096, fp.size());
|
||||
auto read = std::min<uint64_t>(65536, fp.size());
|
||||
auto buf = fp.read(0, read);
|
||||
for (size_t i = 0; i < read; ++i) {
|
||||
if ((unsigned char)buf[i] < 32 && (buf[i] != '\r' && buf[i] != '\n' && buf[i] != '\t'))
|
||||
|
|
|
@ -366,7 +366,7 @@ size_t IconvWrapper::RequiredBufferSize(std::string const& str) {
|
|||
}
|
||||
|
||||
size_t IconvWrapper::RequiredBufferSize(const char* src, size_t srcLen) {
|
||||
char buff[512];
|
||||
char buff[65536];
|
||||
size_t charsWritten = 0;
|
||||
size_t res;
|
||||
|
||||
|
@ -420,7 +420,7 @@ size_t IconvWrapper::DstStrLen(const char* str) {
|
|||
bool IsConversionSupported(const char *src, const char *dst) {
|
||||
iconv_t cd = iconv_open(dst, src);
|
||||
bool supported = cd != iconv_invalid;
|
||||
iconv_close(cd);
|
||||
if (supported) iconv_close(cd);
|
||||
return supported;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,26 @@ namespace ec = boost::system::errc;
|
|||
// boost::filesystem functions throw a single exception type for all
|
||||
// errors, which isn't really what we want, so do some crazy wrapper
|
||||
// shit to map error codes to more useful exceptions.
|
||||
#ifdef BOOST_WINDOWS_API
|
||||
#define CHECKED_CALL(exp, src_path, dst_path) \
|
||||
boost::system::error_code ec; \
|
||||
exp; \
|
||||
switch (ec.value()) {\
|
||||
case ERROR_SUCCESS: break; \
|
||||
case ERROR_FILE_NOT_FOUND: throw FileNotFound(src_path); \
|
||||
case ERROR_DIRECTORY: throw NotADirectory(src_path); \
|
||||
case ERROR_DISK_FULL: throw DriveFull(dst_path); \
|
||||
case ERROR_ACCESS_DENIED: \
|
||||
if (!src_path.empty()) \
|
||||
acs::CheckFileRead(src_path); \
|
||||
if (!dst_path.empty()) \
|
||||
acs::CheckFileWrite(dst_path); \
|
||||
throw AccessDenied(src_path); \
|
||||
default: \
|
||||
LOG_D("filesystem") << "Unknown error when calling '" << #exp << "': " << ec << ": " << ec.message(); \
|
||||
throw FileSystemUnknownError(ec.message()); \
|
||||
}
|
||||
#else
|
||||
#define CHECKED_CALL(exp, src_path, dst_path) \
|
||||
boost::system::error_code ec; \
|
||||
exp; \
|
||||
|
@ -49,6 +69,7 @@ namespace ec = boost::system::errc;
|
|||
LOG_D("filesystem") << "Unknown error when calling '" << #exp << "': " << ec << ": " << ec.message(); \
|
||||
throw FileSystemUnknownError(ec.message()); \
|
||||
}
|
||||
#endif
|
||||
|
||||
#define CHECKED_CALL_RETURN(exp, src_path) \
|
||||
CHECKED_CALL(auto ret = exp, src_path, agi::fs::path()); \
|
||||
|
|
|
@ -56,7 +56,8 @@ Save::Save(fs::path const& file, bool binary)
|
|||
}
|
||||
}
|
||||
|
||||
Save::~Save() noexcept(false) {
|
||||
void Save::Close() {
|
||||
if (!fp) return;
|
||||
fp.reset(); // Need to close before rename on Windows to unlock the file
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
try {
|
||||
|
@ -72,5 +73,12 @@ Save::~Save() noexcept(false) {
|
|||
}
|
||||
}
|
||||
|
||||
Save::~Save() {
|
||||
try {
|
||||
Close();
|
||||
}
|
||||
catch (agi::fs::FileSystemError const&) {}
|
||||
}
|
||||
|
||||
} // namespace io
|
||||
} // namespace agi
|
||||
|
|
|
@ -81,13 +81,19 @@ decltype(LogSink::messages) LogSink::GetMessages() const {
|
|||
return ret;
|
||||
}
|
||||
|
||||
#ifdef LOG_WITH_FILE
|
||||
Message::Message(const char *section, Severity severity, const char *file, const char *func, int line)
|
||||
#else
|
||||
Message::Message(const char* section, Severity severity, const char* func, int line)
|
||||
#endif
|
||||
: msg(buffer, sizeof buffer)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
sm.section = section;
|
||||
sm.severity = severity;
|
||||
#ifdef LOG_WITH_FILE
|
||||
sm.file = file;
|
||||
#endif
|
||||
sm.func = func;
|
||||
sm.line = line;
|
||||
sm.time = duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count();
|
||||
|
@ -109,7 +115,9 @@ void JsonEmitter::log(SinkMessage const& sm) {
|
|||
entry["usec"] = sm.time % 1000000000;
|
||||
entry["severity"] = sm.severity;
|
||||
entry["section"] = sm.section;
|
||||
#ifdef LOG_WITH_FILE
|
||||
entry["file"] = sm.file;
|
||||
#endif
|
||||
entry["func"] = sm.func;
|
||||
entry["line"] = sm.line;
|
||||
entry["message"] = sm.message;
|
||||
|
|
|
@ -173,7 +173,7 @@ void tagless_find_helper::map_range(size_t &s, size_t &e) {
|
|||
// match
|
||||
for (auto const& block : blocks) {
|
||||
// Any blocks before start are irrelevant as they're included in `start`
|
||||
if (block.second < s) continue;
|
||||
if (block.second <= start) continue;
|
||||
// Skip over blocks at the very beginning of the match
|
||||
// < should only happen if the cursor was within an override block
|
||||
// when the user started a search
|
||||
|
|
|
@ -28,16 +28,14 @@ public:
|
|||
Time(std::string const& text);
|
||||
|
||||
/// Get millisecond, rounded to centisecond precision
|
||||
operator int() const { return time / 10 * 10; }
|
||||
|
||||
int GetTimeHours() const; ///< Get the hours portion of this time
|
||||
int GetTimeMinutes() const; ///< Get the minutes portion of this time
|
||||
int GetTimeSeconds() const; ///< Get the seconds portion of this time
|
||||
int GetTimeMiliseconds() const; ///< Get the miliseconds portion of this time
|
||||
int GetTimeCentiseconds() const; ///< Get the centiseconds portion of this time
|
||||
// Always round up for 5ms because the range is [start, stop)
|
||||
operator int() const { return (time + 5) - (time + 5) % 10; }
|
||||
|
||||
/// Return the time as a string
|
||||
/// @param ms Use milliseconds precision, for non-ASS formats
|
||||
std::string GetAssFormatted(bool ms=false) const;
|
||||
|
||||
/// Return the time as a string
|
||||
std::string GetSrtFormatted() const;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ protected:
|
|||
bool float_samples = false;
|
||||
|
||||
virtual void FillBuffer(void *buf, int64_t start, int64_t count) const = 0;
|
||||
virtual void FillBufferInt16Mono(int16_t* buf, int64_t start, int64_t count) const;
|
||||
|
||||
void ZeroFill(void *buf, int64_t count) const;
|
||||
|
||||
|
@ -43,7 +44,8 @@ public:
|
|||
virtual ~AudioProvider() = default;
|
||||
|
||||
void GetAudio(void *buf, int64_t start, int64_t count) const;
|
||||
void GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const;
|
||||
void GetInt16MonoAudio(int16_t* buf, int64_t start, int64_t count) const;
|
||||
void GetInt16MonoAudioWithVolume(int16_t *buf, int64_t start, int64_t count, double volume) const;
|
||||
|
||||
int64_t GetNumSamples() const { return num_samples; }
|
||||
int64_t GetDecodedSamples() const { return decoded_samples; }
|
||||
|
|
|
@ -98,12 +98,6 @@ namespace agi {
|
|||
std::string const& GetMessage() const { return message; }
|
||||
};
|
||||
|
||||
/// @brief Convenience macro to include the current location in code
|
||||
///
|
||||
/// Intended for use in error messages where it can sometimes be convenient to
|
||||
/// indicate the exact position the error occurred at.
|
||||
#define AG_WHERE " (at " __FILE__ ":" #__LINE__ ")"
|
||||
|
||||
/// @brief Convenience macro for declaring exceptions
|
||||
/// @param classname Name of the exception class to declare
|
||||
/// @param baseclass Class to derive from
|
||||
|
|
|
@ -53,7 +53,7 @@ namespace agi {
|
|||
DEFINE_FS_EXCEPTION(FileNotFound, FileNotAccessible, "File not found: ");
|
||||
|
||||
/// An error of some unknown type has occured
|
||||
DEFINE_EXCEPTION(FileSystemUnknownError, FileSystemError);;
|
||||
DEFINE_EXCEPTION(FileSystemUnknownError, FileSystemError);
|
||||
|
||||
/// The path exists, but isn't a file
|
||||
DEFINE_FS_EXCEPTION(NotAFile, FileNotAccessible, "Path is not a file (and should be): ");
|
||||
|
|
|
@ -38,8 +38,9 @@ class Save {
|
|||
|
||||
public:
|
||||
Save(fs::path const& file, bool binary = false);
|
||||
~Save() noexcept(false);
|
||||
~Save();
|
||||
std::ostream& Get() { return *fp; }
|
||||
void Close();
|
||||
};
|
||||
|
||||
} // namespace io
|
||||
|
|
|
@ -21,7 +21,11 @@
|
|||
|
||||
// These macros below aren't a perm solution, it will depend on how annoying they are through
|
||||
// actual usage, and also depends on msvc support.
|
||||
#ifdef LOG_WITH_FILE
|
||||
#define LOG_SINK(section, severity) agi::log::Message(section, severity, __FILE__, __FUNCTION__, __LINE__).stream()
|
||||
#else
|
||||
#define LOG_SINK(section, severity) agi::log::Message(section, severity, __FUNCTION__, __LINE__).stream()
|
||||
#endif
|
||||
#define LOG_E(section) LOG_SINK(section, agi::log::Exception)
|
||||
#define LOG_A(section) LOG_SINK(section, agi::log::Assert)
|
||||
#define LOG_W(section) LOG_SINK(section, agi::log::Warning)
|
||||
|
@ -59,7 +63,9 @@ struct SinkMessage {
|
|||
std::string message; ///< Formatted message
|
||||
int64_t time; ///< Time at execution in nanoseconds since epoch
|
||||
const char *section; ///< Section info eg "video/open" "video/seek" etc
|
||||
#ifdef LOG_WITH_FILE
|
||||
const char *file; ///< Source file
|
||||
#endif
|
||||
const char *func; ///< Function name
|
||||
Severity severity; ///< Severity
|
||||
int line; ///< Source line
|
||||
|
@ -125,7 +131,11 @@ class Message {
|
|||
char buffer[2048];
|
||||
|
||||
public:
|
||||
#ifdef LOG_WITH_FILE
|
||||
Message(const char* section, Severity severity, const char* file, const char* func, int line);
|
||||
#else
|
||||
Message(const char* section, Severity severity, const char* func, int line);
|
||||
#endif
|
||||
~Message();
|
||||
std::ostream& stream() { return msg; }
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#ifdef __cplusplus
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !defined(CMAKE_BUILD)
|
||||
#include "../acconf.h"
|
||||
#endif
|
||||
|
||||
|
|
|
@ -2334,7 +2334,7 @@ static int matchl (lua_State *L) {
|
|||
}
|
||||
|
||||
|
||||
static struct luaL_reg pattreg[] = {
|
||||
static struct luaL_Reg pattreg[] = {
|
||||
{"match", matchl},
|
||||
{"print", printpat_l},
|
||||
{"locale", locale_l},
|
||||
|
@ -2360,7 +2360,7 @@ static struct luaL_reg pattreg[] = {
|
|||
};
|
||||
|
||||
|
||||
static struct luaL_reg metapattreg[] = {
|
||||
static struct luaL_Reg metapattreg[] = {
|
||||
{"__add", union_l},
|
||||
{"__pow", star_l},
|
||||
{"__sub", diff_l},
|
||||
|
|
|
@ -24,14 +24,20 @@ void EmitSTDOUT::log(SinkMessage const& sm) {
|
|||
tm tmtime;
|
||||
localtime_r(&time, &tmtime);
|
||||
|
||||
#ifdef LOG_WITH_FILE
|
||||
printf("%c %02d:%02d:%02d %-9ld <%-25s> [%s:%s:%d] %.*s\n",
|
||||
#else
|
||||
printf("%c %02d:%02d:%02d %-9ld <%-25s> [%s:%d] %.*s\n",
|
||||
#endif
|
||||
Severity_ID[sm.severity],
|
||||
tmtime.tm_hour,
|
||||
tmtime.tm_min,
|
||||
tmtime.tm_sec,
|
||||
(long)(sm.time % 1000000000),
|
||||
sm.section,
|
||||
#ifdef LOG_WITH_FILE
|
||||
sm.file,
|
||||
#endif
|
||||
sm.func,
|
||||
sm.line,
|
||||
(int)sm.message.size(),
|
||||
|
|
|
@ -27,8 +27,12 @@ void EmitSTDOUT::log(SinkMessage const& sm) {
|
|||
localtime_s(&tmtime, &time);
|
||||
|
||||
char buff[65536];
|
||||
#ifdef LOG_WITH_FILE
|
||||
_snprintf_s(buff, _TRUNCATE, "%s (%d): %c %02d:%02d:%02d.%-3ld <%-25s> [%s] %.*s\n",
|
||||
sm.file,
|
||||
#else
|
||||
_snprintf_s(buff, _TRUNCATE, "Line %d: %c %02d:%02d:%02d.%-3ld <%-25s> [%s] %.*s\n",
|
||||
#endif
|
||||
sm.line,
|
||||
Severity_ID[sm.severity],
|
||||
tmtime.tm_hour,
|
||||
|
|
|
@ -10,15 +10,26 @@ DESKTOP_FILE_INSTALLED = $(DESTDIR)$(P_DESKTOP)/$(notdir $(DESKTOP_FILE))
|
|||
|
||||
DISTCLEANFILES += $(DESKTOP_FILE)
|
||||
|
||||
APPDATA_FILE := $(d)aegisub.appdata.xml
|
||||
APPDATA_FILE_PO := $(d)../../po
|
||||
APPDATA_FILE_INSTALLED = $(DESTDIR)$(P_APPDATA)/$(notdir $(APPDATA_FILE))
|
||||
|
||||
DISTCLEANFILES += $(APPDATA_FILE)
|
||||
|
||||
%.desktop: %.desktop.template $(DESKTOP_FILE_PO)
|
||||
intltool-merge --quiet --desktop-style $(DESKTOP_FILE_PO) $< $@
|
||||
$(AM_V_GEN)$(BIN_MSGFMT) --desktop --template $< -d $(TOP)po -o $@
|
||||
|
||||
%.appdata.xml: %.appdata.xml.template $(APPDATA_FILE_PO)
|
||||
intltool-merge --quiet --xml-style $(APPDATA_FILE_PO) $< $@
|
||||
|
||||
$(ICONS_INSTALLED)png: $(d)%.png ; $(MKDIR_INSTALL)
|
||||
$(ICONS_INSTALLED)svg: $(d)%.svg ; $(MKDIR_INSTALL)
|
||||
$(DESKTOP_FILE_INSTALLED): $(DESKTOP_FILE) ; $(MKDIR_INSTALL)
|
||||
$(APPDATA_FILE_INSTALLED): $(APPDATA_FILE) ; $(MKDIR_INSTALL)
|
||||
|
||||
ifneq (yes, $(BUILD_DARWIN))
|
||||
install: \
|
||||
$(APPDATA_FILE_INSTALLED) \
|
||||
$(DESKTOP_FILE_INSTALLED) \
|
||||
$(patsubst %.png, $(ICONS_INSTALLED)png, $(patsubst %.svg, $(ICONS_INSTALLED)svg, $(notdir $(ICONS))))
|
||||
endif
|
||||
|
|
58
packages/desktop/aegisub.appdata.xml.template.in
Normal file
58
packages/desktop/aegisub.appdata.xml.template.in
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop">
|
||||
<id>aegisub.desktop</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>BSD-3-Clause AND MIT AND MPL-1.1</project_license>
|
||||
<_name>Aegisub</_name>
|
||||
<_summary>A free, cross-platform open source tool for creating and modifying subtitles</_summary>
|
||||
<description>
|
||||
<_p>Aegisub is a free, cross-platform open source tool for creating and modifying subtitles. Aegisub makes it quick and easy to time subtitles to audio, and features many powerful tools for styling them, including a built-in real-time video preview.</_p>
|
||||
<_p>Aegisub was originally created as a tool to make typesetting, particularly in anime fansubs, a less painful experience. At the time of the start of the project, many other programs that supported the Advanced Substation Alpha format lacked (and in many cases, still lack; development on several competing programs have since been dropped for various reasons completely unrelated to Aegisub) many vital functions, or were too buggy and/or unreliable to be really useful.</_p>
|
||||
<_p>Since then, Aegisub has grown into a fully fledged, highly customizable subtitle editor. It features a lot of convenient tools to help you with timing, typesetting, editing and translating subtitles, as well as a powerful scripting environment called Automation (originally mostly intended for creating karaoke effects, Automation can now be used much else, including creating macros and various other convenient tools).</_p>
|
||||
<_p>Some highlights of Aegisub:</_p>
|
||||
<ul>
|
||||
<_li>Simple and intuitive yet powerful interface for editing subtitles</_li>
|
||||
<_li>Support for many formats and character sets</_li>
|
||||
<_li>Powerful video mode</_li>
|
||||
<_li>Visual typesetting tools</_li>
|
||||
<_li>Intuitive and customizable audio timing mode</_li>
|
||||
<_li>Fully scriptable through the Automation module</_li>
|
||||
</ul>
|
||||
</description>
|
||||
<launchable type="desktop-id">aegisub.desktop</launchable>
|
||||
<kudos>
|
||||
<kudo>HiDpiIcon</kudo>
|
||||
<kudo>HighContrast</kudo>
|
||||
<kudo>UserDocs</kudo>
|
||||
</kudos>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<_caption>Typesetting</_caption>
|
||||
<image>http://static.aegisub.org/img/screenshots/unix/typesetting-efc51b7a.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<_caption>Audio video</_caption>
|
||||
<image>http://static.aegisub.org/img/screenshots/unix/audio-video-f1f81fc2.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<_caption>Audio timing</_caption>
|
||||
<image>http://static.aegisub.org/img/screenshots/unix/audio-timing-1d8fce7e.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<developer_name>Aegisub Group</developer_name>
|
||||
<url type="bugtracker">https://github.com/Aegisub/Aegisub/issues</url>
|
||||
<url type="faq">http://docs.aegisub.org/manual/FAQ</url>
|
||||
<url type="help">http://docs.aegisub.org</url>
|
||||
<url type="homepage">http://www.aegisub.org</url>
|
||||
<url type="translate">https://sites.google.com/site/rockytdrontransifex/aegisub</url>
|
||||
<content_rating type="oars-1.0">
|
||||
<content_attribute id="social-info">mild</content_attribute>
|
||||
</content_rating>
|
||||
<translation type="gettext">aegisub</translation>
|
||||
<provides>
|
||||
<binary>aegisub</binary>
|
||||
</provides>
|
||||
<releases>
|
||||
<release version="3.2.2" date="2014-12-08"/>
|
||||
</releases>
|
||||
</component>
|
|
@ -1,13 +1,15 @@
|
|||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
_Name=Aegisub
|
||||
_GenericName=Subtitle Editor
|
||||
_Comment=Create and edit subtitles for film and videos.
|
||||
Exec=@AEGISUB_COMMAND@ %f
|
||||
Name=Aegisub
|
||||
GenericName=Subtitle Editor
|
||||
Comment=Create and edit subtitles for film and videos.
|
||||
Exec=env GDK_BACKEND=x11 @AEGISUB_COMMAND@ %f
|
||||
TryExec=@AEGISUB_COMMAND@
|
||||
Icon=aegisub
|
||||
Terminal=false
|
||||
Categories=AudioVideo;AudioVideoEditing;
|
||||
Keywords=subtitles;subtitle;captions;captioning;video;audio;
|
||||
MimeType=application/x-srt;text/plain;text/x-ass;text/x-microdvd;text/x-ssa;
|
||||
StartupNotify=true
|
||||
StartupWMClass=aegisub
|
||||
|
|
|
@ -64,6 +64,11 @@ Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub{#ARCH}.exe\SupportedT
|
|||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub{#ARCH}.exe\SupportedTypes"; ValueType: string; ValueName: ".wav"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub{#ARCH}.exe\SupportedTypes"; ValueType: string; ValueName: ".ogg"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub{#ARCH}.exe\SupportedTypes"; ValueType: string; ValueName: ".avs"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub{#ARCH}.exe\SupportedTypes"; ValueType: string; ValueName: ".opus"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub{#ARCH}.exe\SupportedTypes"; ValueType: string; ValueName: ".h264"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub{#ARCH}.exe\SupportedTypes"; ValueType: string; ValueName: ".hevc"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub{#ARCH}.exe\SupportedTypes"; ValueType: string; ValueName: ".eac3"; ValueData: ""; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Applications\aegisub{#ARCH}.exe\SupportedTypes"; ValueType: string; ValueName: ".webm"; ValueData: ""; Flags: uninsdeletekey
|
||||
; Class for general subtitle formats
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Aegisub.Subtitle.1"; ValueType: string; ValueName: ""; ValueData: "Aegisub subtitle file"; Flags: uninsdeletekey
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\Aegisub.Subtitle.1"; ValueType: dword; ValueName: "EditFlags"; ValueData: $af0; Flags: uninsdeletekey
|
||||
|
@ -195,4 +200,8 @@ Root: HKLM; Subkey: "SOFTWARE\Classes\.m4a\OpenWithProgids"; ValueType: string;
|
|||
Root: HKLM; Subkey: "SOFTWARE\Classes\.wav\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Audio.1"; Flags: uninsdeletevalue
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.ogg\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Media.1"; Flags: uninsdeletevalue
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.avs\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Video.1"; Flags: uninsdeletevalue
|
||||
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.opus\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Audio.1"; Flags: uninsdeletevalue
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.h264\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Video.1"; Flags: uninsdeletevalue
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.hevc\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Video.1"; Flags: uninsdeletevalue
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.eac3\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Audio.1"; Flags: uninsdeletevalue
|
||||
Root: HKLM; Subkey: "SOFTWARE\Classes\.webm\OpenWithProgids"; ValueType: string; ValueName: "Aegisub.Media.1"; Flags: uninsdeletevalue
|
||||
|
|
2
po/LINGUAS
Normal file
2
po/LINGUAS
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Set of available languages.
|
||||
ar be bg ca cs da de el es eu fa fi fr_FR gl hu id it ja ko nl pl pt_BR pt_PT ru sr_RS@latin sr_RS uk_UA vi zh_CN zh_TW
|
|
@ -7,7 +7,7 @@ maybe_append() {
|
|||
msgid=$(echo $msg | cut -d'|' -f3-)
|
||||
|
||||
if ! grep -Fq "msgid $msgid" aegisub.pot; then
|
||||
echo "\n#: $msgfile:$msgline\nmsgid $msgid\nmsgstr \"\"\n" >> aegisub.pot
|
||||
echo -e "\n#: $msgfile:$msgline\nmsgid $msgid\nmsgstr \"\"\n" >> aegisub.pot
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
@ -33,18 +33,19 @@ grep '"[A-Za-z ]\+" : {' -n ../src/libresrc/default_hotkey.json \
|
|||
| sed 's/^\([0-9]\+:\).*\("[^"]\+"\).*$/default_hotkey.json|\1|\2/' \
|
||||
| maybe_append
|
||||
|
||||
find ../automation -name *.lua \
|
||||
| xargs grep tr\"[^\"]\*\" -o -n \
|
||||
find ../automation -name '*.lua' \
|
||||
| xargs grep 'tr"[^"]*"' -o -n \
|
||||
| sed 's/\(.*\):\([0-9]\+\):tr\(".*"\)/\1|\2|\3/' \
|
||||
| sed 's/\\/\\\\\\\\/g' \
|
||||
| maybe_append
|
||||
|
||||
for i in 'Name' 'GenericName' 'Comment'
|
||||
do
|
||||
grep ^_$i -n ../packages/desktop/aegisub.desktop.template.in \
|
||||
| sed 's/\([0-9]\+\):[^=]\+=\(.*\)$/aegisub.desktop|\1|"\2"/' \
|
||||
| maybe_append
|
||||
done
|
||||
xgettext ../packages/desktop/aegisub.desktop.template --language=Desktop --join-existing -o aegisub.pot
|
||||
|
||||
if which xmlstarlet >/dev/null 2>&1 && which jq >/dev/null 2>&1; then
|
||||
for i in 'name' 'summary' 'p' 'li' 'caption'; do
|
||||
xmlstarlet sel -t -v "//_$i" ../packages/desktop/aegisub.appdata.xml.template.in | jq -R .
|
||||
done | nl -v0 -w1 -s'|' | sed -re 's/^/aegisub.appdata.xml|/' | maybe_append
|
||||
fi
|
||||
|
||||
grep '^_[A-Za-z0-9]*=.*' ../packages/win_installer/fragment_strings.iss.in | while read line
|
||||
do
|
||||
|
|
|
@ -66,6 +66,7 @@ src_OBJ := \
|
|||
$(d)help_button.o \
|
||||
$(d)hotkey.o \
|
||||
$(d)hotkey_data_view_model.o \
|
||||
$(d)image_position_picker.o \
|
||||
$(d)initial_line_state.o \
|
||||
$(d)main.o \
|
||||
$(d)menu.o \
|
||||
|
@ -188,14 +189,13 @@ endif
|
|||
#####################
|
||||
# SOURCE-LEVEL CFLAGS
|
||||
#####################
|
||||
$(d)MatroskaParser.o_FLAGS := -Wno-sometimes-uninitialized
|
||||
$(d)audio_player.o_FLAGS := $(CFLAGS_ALSA) $(CFLAGS_PORTAUDIO) $(CFLAGS_LIBPULSE) $(CFLAGS_OPENAL)
|
||||
$(d)audio_provider_factory.o_FLAGS := $(CFLAGS_FFMS2)
|
||||
$(d)auto4_base.o_FLAGS := $(CFLAGS_FREETYPE)
|
||||
$(d)charset_detect.o_FLAGS := -D_X86_
|
||||
$(d)font_file_lister_fontconfig.o_FLAGS := $(CFLAGS_FONTCONFIG)
|
||||
$(d)subtitles_provider.o_FLAGS := $(CFLAGS_LIBASS)
|
||||
$(d)subtitles_provider_libass.o_FLAGS := $(CFLAGS_LIBASS) -Wno-c++11-narrowing
|
||||
$(d)subtitles_provider_libass.o_FLAGS := $(CFLAGS_LIBASS) -Wno-narrowing
|
||||
$(d)text_file_reader.o_FLAGS := -D_X86_
|
||||
$(d)video_provider_manager.o_FLAGS := $(CFLAGS_FFMS2)
|
||||
$(d)auto4_lua.o_FLAGS := $(CFLAGS_LUA)
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !defined(CMAKE_BUILD)
|
||||
#include "../acconf.h"
|
||||
#endif
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ class AssStyle final : public AssEntry, public AssEntryListHook {
|
|||
public:
|
||||
std::string name = "Default"; ///< Name of the style; must be case-insensitively unique within a file despite being case-sensitive
|
||||
std::string font = "Arial"; ///< Font face name
|
||||
double fontsize = 20.; ///< Font size
|
||||
double fontsize = 48.; ///< Font size
|
||||
|
||||
agi::Color primary{ 255, 255, 255 }; ///< Default text color
|
||||
agi::Color secondary{ 255, 0, 0 }; ///< Text color for not-yet-reached karaoke syllables
|
||||
|
|
|
@ -129,10 +129,9 @@ END_EVENT_TABLE()
|
|||
void AudioBox::OnMouseWheel(wxMouseEvent &evt) {
|
||||
if (!ForwardMouseWheelEvent(audioDisplay, evt))
|
||||
return;
|
||||
|
||||
bool zoom = evt.CmdDown() != OPT_GET("Audio/Wheel Default to Zoom")->GetBool();
|
||||
if (!zoom) {
|
||||
int amount = -evt.GetWheelRotation() * GetClientSize().GetWidth() / (evt.GetWheelDelta() * 3);
|
||||
int amount = -evt.GetWheelRotation();
|
||||
// If the user did a horizontal scroll the amount should be inverted
|
||||
// for it to be natural.
|
||||
if (evt.GetWheelAxis() == 1) amount = -amount;
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
AudioColorScheme::AudioColorScheme(int prec, std::string const& scheme_name, int audio_rendering_style)
|
||||
: palette((3<<prec) + 3)
|
||||
, factor(1<<prec)
|
||||
, factor((size_t)1<<prec)
|
||||
{
|
||||
std::string opt_base = "Colour/Schemes/" + scheme_name + "/";
|
||||
switch (static_cast<AudioRenderingStyle>(audio_rendering_style))
|
||||
|
|
|
@ -128,8 +128,9 @@ public:
|
|||
/// Get the current Selection colour
|
||||
wxColour Selection() const { return focused ? sel_focused_colour : sel_colour; }
|
||||
};
|
||||
}
|
||||
|
||||
class AudioDisplayScrollbar final : public AudioDisplayInteractionObject {
|
||||
class AudioDisplay::AudioDisplayScrollbar final : public AudioDisplayInteractionObject {
|
||||
static const int height = 15;
|
||||
static const int min_width = 10;
|
||||
|
||||
|
@ -153,7 +154,7 @@ class AudioDisplayScrollbar final : public AudioDisplayInteractionObject {
|
|||
// Recalculate thumb bounds from position and length data
|
||||
void RecalculateThumb()
|
||||
{
|
||||
thumb.width = std::max<int>(min_width, (int64_t)bounds.width * page_length / data_length);
|
||||
thumb.width = int((int64_t)bounds.width * page_length / data_length);
|
||||
thumb.height = height;
|
||||
thumb.x = int((int64_t)bounds.width * position / data_length);
|
||||
thumb.y = bounds.y;
|
||||
|
@ -263,13 +264,18 @@ public:
|
|||
|
||||
dc.SetPen(wxPen(colours.Light()));
|
||||
dc.SetBrush(wxBrush(colours.Light()));
|
||||
|
||||
// Paint the thumb at least min_width, expand to both left and right
|
||||
if (thumb.width < min_width)
|
||||
dc.DrawRectangle(wxRect(thumb.x - (min_width - thumb.width) / 2, thumb.y, min_width, thumb.height));
|
||||
else
|
||||
dc.DrawRectangle(thumb);
|
||||
}
|
||||
};
|
||||
|
||||
const int AudioDisplayScrollbar::min_width;
|
||||
const int AudioDisplay::AudioDisplayScrollbar::min_width;
|
||||
|
||||
class AudioDisplayTimeline final : public AudioDisplayInteractionObject {
|
||||
class AudioDisplay::AudioDisplayTimeline final : public AudioDisplayInteractionObject {
|
||||
int duration = 0; ///< Total duration in ms
|
||||
double ms_per_pixel = 1.0; ///< Milliseconds per pixel
|
||||
int pixel_left = 0; ///< Leftmost visible pixel (i.e. scroll position)
|
||||
|
@ -478,6 +484,7 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
class AudioStyleRangeMerger final : public AudioRenderingStyleRanges {
|
||||
typedef std::map<int, AudioRenderingStyle> style_map;
|
||||
public:
|
||||
|
|
|
@ -47,11 +47,6 @@ class AudioRenderer;
|
|||
class AudioRendererBitmapProvider;
|
||||
class TimeRange;
|
||||
|
||||
// Helper classes used in implementation of the audio display
|
||||
namespace {
|
||||
class AudioDisplayScrollbar;
|
||||
class AudioDisplayTimeline;
|
||||
}
|
||||
class AudioDisplayInteractionObject;
|
||||
class AudioMarkerInteractionObject;
|
||||
|
||||
|
@ -79,9 +74,11 @@ class AudioDisplay: public wxWindow {
|
|||
agi::AudioProvider *provider = nullptr;
|
||||
|
||||
/// Scrollbar helper object
|
||||
class AudioDisplayScrollbar;
|
||||
std::unique_ptr<AudioDisplayScrollbar> scrollbar;
|
||||
|
||||
/// Timeline helper object
|
||||
class AudioDisplayTimeline;
|
||||
std::unique_ptr<AudioDisplayTimeline> timeline;
|
||||
|
||||
/// The interaction object for the last-dragged audio marker
|
||||
|
|
|
@ -280,8 +280,7 @@ void AudioKaraoke::OnMouse(wxMouseEvent &event) {
|
|||
split_area->Refresh(false);
|
||||
scroll_timer.Start(50);
|
||||
split_area->CaptureMouse();
|
||||
wxTimerEvent evt;
|
||||
OnScrollTimer(evt);
|
||||
OnScrollTimer();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -317,7 +316,7 @@ void AudioKaraoke::OnMouse(wxMouseEvent &event) {
|
|||
split_area->Refresh(false);
|
||||
}
|
||||
|
||||
void AudioKaraoke::OnScrollTimer(wxTimerEvent &) {
|
||||
void AudioKaraoke::OnScrollTimer() {
|
||||
scroll_x += scroll_dir * char_width * 3;
|
||||
|
||||
int max_scroll = rendered_line.GetWidth() + 20 - split_area->GetClientSize().GetWidth();
|
||||
|
@ -329,6 +328,10 @@ void AudioKaraoke::OnScrollTimer(wxTimerEvent &) {
|
|||
split_area->Refresh(false);
|
||||
}
|
||||
|
||||
void AudioKaraoke::OnScrollTimer(wxTimerEvent&) {
|
||||
OnScrollTimer();
|
||||
}
|
||||
|
||||
void AudioKaraoke::LoadFromLine() {
|
||||
scroll_x = 0;
|
||||
scroll_timer.Stop();
|
||||
|
|
|
@ -142,6 +142,7 @@ class AudioKaraoke final : public wxWindow {
|
|||
void OnPaint(wxPaintEvent &event);
|
||||
void OnSize(wxSizeEvent &event);
|
||||
void OnAudioOpened(agi::AudioProvider *provider);
|
||||
void OnScrollTimer();
|
||||
void OnScrollTimer(wxTimerEvent& event);
|
||||
|
||||
public:
|
||||
|
|
|
@ -40,13 +40,28 @@
|
|||
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
#ifdef WITH_ALSA
|
||||
std::unique_ptr<AudioPlayer> CreateAlsaPlayer(agi::AudioProvider *providers, wxWindow *window);
|
||||
#endif
|
||||
#ifdef WITH_DIRECTSOUND
|
||||
std::unique_ptr<AudioPlayer> CreateDirectSoundPlayer(agi::AudioProvider *providers, wxWindow *window);
|
||||
std::unique_ptr<AudioPlayer> CreateDirectSound2Player(agi::AudioProvider *providers, wxWindow *window);
|
||||
#endif
|
||||
#ifdef WITH_XAUDIO2
|
||||
std::unique_ptr<AudioPlayer> CreateXAudio2Player(agi::AudioProvider* providers, wxWindow* window);
|
||||
#endif
|
||||
#ifdef WITH_OPENAL
|
||||
std::unique_ptr<AudioPlayer> CreateOpenALPlayer(agi::AudioProvider *providers, wxWindow *window);
|
||||
#endif
|
||||
#ifdef WITH_PORTAUDIO
|
||||
std::unique_ptr<AudioPlayer> CreatePortAudioPlayer(agi::AudioProvider *providers, wxWindow *window);
|
||||
#endif
|
||||
#ifdef WITH_LIBPULSE
|
||||
std::unique_ptr<AudioPlayer> CreatePulseAudioPlayer(agi::AudioProvider *providers, wxWindow *window);
|
||||
#endif
|
||||
#ifdef WITH_OSS
|
||||
std::unique_ptr<AudioPlayer> CreateOSSPlayer(agi::AudioProvider *providers, wxWindow *window);
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
struct factory {
|
||||
|
@ -63,6 +78,9 @@ namespace {
|
|||
{"DirectSound-old", CreateDirectSoundPlayer, false},
|
||||
{"DirectSound", CreateDirectSound2Player, false},
|
||||
#endif
|
||||
#ifdef WITH_XAUDIO2
|
||||
{"XAudio2", CreateXAudio2Player, false},
|
||||
#endif
|
||||
#ifdef WITH_OPENAL
|
||||
{"OpenAL", CreateOpenALPlayer, false},
|
||||
#endif
|
||||
|
|
|
@ -127,7 +127,7 @@ void AlsaPlayer::PlaybackThread()
|
|||
|
||||
do_setup:
|
||||
snd_pcm_format_t pcm_format;
|
||||
switch (provider->GetBytesPerSample())
|
||||
switch (/*provider->GetBytesPerSample()*/ sizeof(int16_t))
|
||||
{
|
||||
case 1:
|
||||
LOG_D("audio/player/alsa") << "format U8";
|
||||
|
@ -143,7 +143,7 @@ do_setup:
|
|||
if (snd_pcm_set_params(pcm,
|
||||
pcm_format,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED,
|
||||
provider->GetChannels(),
|
||||
/*provider->GetChannels()*/ 1,
|
||||
provider->GetSampleRate(),
|
||||
1, // allow resample
|
||||
100*1000 // 100 milliseconds latency
|
||||
|
@ -151,7 +151,8 @@ do_setup:
|
|||
return;
|
||||
LOG_D("audio/player/alsa") << "set pcm params";
|
||||
|
||||
size_t framesize = provider->GetChannels() * provider->GetBytesPerSample();
|
||||
//size_t framesize = provider->GetChannels() * provider->GetBytesPerSample();
|
||||
size_t framesize = sizeof(int16_t);
|
||||
|
||||
while (true)
|
||||
{
|
||||
|
@ -175,7 +176,7 @@ do_setup:
|
|||
{
|
||||
auto avail = std::min(snd_pcm_avail(pcm), (snd_pcm_sframes_t)(end_position-position));
|
||||
decode_buffer.resize(avail * framesize);
|
||||
provider->GetAudioWithVolume(decode_buffer.data(), position, avail, volume);
|
||||
provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(decode_buffer.data()), position, avail, volume);
|
||||
|
||||
snd_pcm_sframes_t written = 0;
|
||||
while (written <= 0)
|
||||
|
@ -235,7 +236,7 @@ do_setup:
|
|||
|
||||
{
|
||||
decode_buffer.resize(avail * framesize);
|
||||
provider->GetAudioWithVolume(decode_buffer.data(), position, avail, volume);
|
||||
provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(decode_buffer.data()), position, avail, volume);
|
||||
snd_pcm_sframes_t written = 0;
|
||||
while (written <= 0)
|
||||
{
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
|
||||
#include <mmsystem.h>
|
||||
#include <dsound.h>
|
||||
#include <cguid.h>
|
||||
|
||||
namespace {
|
||||
class DirectSoundPlayer;
|
||||
|
@ -111,8 +112,10 @@ DirectSoundPlayer::DirectSoundPlayer(agi::AudioProvider *provider, wxWindow *par
|
|||
WAVEFORMATEX waveFormat;
|
||||
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
||||
waveFormat.nSamplesPerSec = provider->GetSampleRate();
|
||||
waveFormat.nChannels = provider->GetChannels();
|
||||
waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8;
|
||||
//waveFormat.nChannels = provider->GetChannels();
|
||||
//waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8;
|
||||
waveFormat.nChannels = 1;
|
||||
waveFormat.wBitsPerSample = sizeof(int16_t) * 8;
|
||||
waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8;
|
||||
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
|
||||
waveFormat.cbSize = sizeof(waveFormat);
|
||||
|
@ -160,7 +163,7 @@ bool DirectSoundPlayer::FillBuffer(bool fill) {
|
|||
HRESULT res;
|
||||
void *ptr1, *ptr2;
|
||||
unsigned long int size1, size2;
|
||||
int bytesps = provider->GetBytesPerSample();
|
||||
int bytesps = /*provider->GetBytesPerSample()*/ sizeof(int16_t);
|
||||
|
||||
// To write length
|
||||
int toWrite = 0;
|
||||
|
@ -223,8 +226,8 @@ RetryLock:
|
|||
LOG_D_IF(!count1 && !count2, "audio/player/dsound1") << "DS fill: nothing";
|
||||
|
||||
// Get source wave
|
||||
if (count1) provider->GetAudioWithVolume(ptr1, playPos, count1, volume);
|
||||
if (count2) provider->GetAudioWithVolume(ptr2, playPos+count1, count2, volume);
|
||||
if (count1) provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(ptr1), playPos, count1, volume);
|
||||
if (count2) provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(ptr2), playPos+count1, count2, volume);
|
||||
playPos += count1+count2;
|
||||
|
||||
buffer->Unlock(ptr1,count1*bytesps,ptr2,count2*bytesps);
|
||||
|
@ -254,7 +257,7 @@ void DirectSoundPlayer::Play(int64_t start,int64_t count) {
|
|||
FillBuffer(true);
|
||||
|
||||
DWORD play_flag = 0;
|
||||
if (count*provider->GetBytesPerSample() > bufSize) {
|
||||
if (count* /*provider->GetBytesPerSample()*/ sizeof(int16_t) > bufSize) {
|
||||
// Start thread
|
||||
thread = new DirectSoundPlayerThread(this);
|
||||
thread->Create();
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
#include <mmsystem.h>
|
||||
#include <process.h>
|
||||
#include <dsound.h>
|
||||
#include <cguid.h>
|
||||
|
||||
namespace {
|
||||
class DirectSoundPlayer2Thread;
|
||||
|
@ -319,8 +320,10 @@ void DirectSoundPlayer2Thread::Run()
|
|||
WAVEFORMATEX waveFormat;
|
||||
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
||||
waveFormat.nSamplesPerSec = provider->GetSampleRate();
|
||||
waveFormat.nChannels = provider->GetChannels();
|
||||
waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8;
|
||||
//waveFormat.nChannels = provider->GetChannels();
|
||||
//waveFormat.wBitsPerSample = provider->GetBytesPerSample() * 8;
|
||||
waveFormat.nChannels = 1;
|
||||
waveFormat.wBitsPerSample = sizeof(int16_t) * 8;
|
||||
waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8;
|
||||
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
|
||||
waveFormat.cbSize = sizeof(waveFormat);
|
||||
|
@ -369,7 +372,7 @@ void DirectSoundPlayer2Thread::Run()
|
|||
DWORD buffer_offset = 0;
|
||||
bool playback_should_be_running = false;
|
||||
int current_latency = wanted_latency;
|
||||
const DWORD wanted_latency_bytes = wanted_latency*waveFormat.nSamplesPerSec*provider->GetBytesPerSample()/1000;
|
||||
const DWORD wanted_latency_bytes = wanted_latency*waveFormat.nSamplesPerSec* /*provider->GetBytesPerSample()*/ sizeof(int16_t)/1000;
|
||||
|
||||
while (running)
|
||||
{
|
||||
|
@ -422,7 +425,7 @@ void DirectSoundPlayer2Thread::Run()
|
|||
if (bytes_filled < wanted_latency_bytes)
|
||||
{
|
||||
// Very short playback length, do without streaming playback
|
||||
current_latency = (bytes_filled*1000) / (waveFormat.nSamplesPerSec*provider->GetBytesPerSample());
|
||||
current_latency = (bytes_filled*1000) / (waveFormat.nSamplesPerSec* /*provider->GetBytesPerSample()*/ sizeof(int16_t));
|
||||
if (FAILED(bfr->Play(0, 0, 0)))
|
||||
REPORT_ERROR("Could not start single-buffer playback.")
|
||||
}
|
||||
|
@ -553,7 +556,7 @@ do_fill_buffer:
|
|||
else if (bytes_filled < wanted_latency_bytes)
|
||||
{
|
||||
// Didn't fill as much as we wanted to, let's get back to filling sooner than normal
|
||||
current_latency = (bytes_filled*1000) / (waveFormat.nSamplesPerSec*provider->GetBytesPerSample());
|
||||
current_latency = (bytes_filled*1000) / (waveFormat.nSamplesPerSec* /*provider->GetBytesPerSample()*/ sizeof(int16_t));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -577,7 +580,7 @@ DWORD DirectSoundPlayer2Thread::FillAndUnlockBuffers(void *buf1, DWORD buf1sz, v
|
|||
{
|
||||
// Assume buffers have been locked and are ready to be filled
|
||||
|
||||
DWORD bytes_per_frame = provider->GetChannels() * provider->GetBytesPerSample();
|
||||
DWORD bytes_per_frame = /*provider->GetChannels() * provider->GetBytesPerSample()*/sizeof(int16_t);
|
||||
DWORD buf1szf = buf1sz / bytes_per_frame;
|
||||
DWORD buf2szf = buf2sz / bytes_per_frame;
|
||||
|
||||
|
@ -608,7 +611,7 @@ DWORD DirectSoundPlayer2Thread::FillAndUnlockBuffers(void *buf1, DWORD buf1sz, v
|
|||
buf2sz = 0;
|
||||
}
|
||||
|
||||
provider->GetAudioWithVolume(buf1, input_frame, buf1szf, volume);
|
||||
provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(buf1), input_frame, buf1szf, volume);
|
||||
|
||||
input_frame += buf1szf;
|
||||
}
|
||||
|
@ -621,7 +624,7 @@ DWORD DirectSoundPlayer2Thread::FillAndUnlockBuffers(void *buf1, DWORD buf1sz, v
|
|||
buf2sz = buf2szf * bytes_per_frame;
|
||||
}
|
||||
|
||||
provider->GetAudioWithVolume(buf2, input_frame, buf2szf, volume);
|
||||
provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(buf2), input_frame, buf2szf, volume);
|
||||
|
||||
input_frame += buf2szf;
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ public:
|
|||
OpenALPlayer::OpenALPlayer(agi::AudioProvider *provider)
|
||||
: AudioPlayer(provider)
|
||||
, samplerate(provider->GetSampleRate())
|
||||
, bpf(provider->GetChannels() * provider->GetBytesPerSample())
|
||||
, bpf(/*provider->GetChannels() * provider->GetBytesPerSample()*/sizeof(int16_t))
|
||||
{
|
||||
device = alcOpenDevice(nullptr);
|
||||
if (!device) throw AudioPlayerOpenError("Failed opening default OpenAL device");
|
||||
|
@ -241,7 +241,7 @@ void OpenALPlayer::FillBuffers(ALsizei count)
|
|||
|
||||
if (fill_len > 0)
|
||||
// Get fill_len frames of audio
|
||||
provider->GetAudioWithVolume(&decode_buffer[0], cur_frame, fill_len, volume);
|
||||
provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(decode_buffer.data()), cur_frame, fill_len, volume);
|
||||
if ((size_t)fill_len * bpf < decode_buffer.size())
|
||||
// And zerofill the rest
|
||||
memset(&decode_buffer[fill_len * bpf], 0, decode_buffer.size() - fill_len * bpf);
|
||||
|
|
|
@ -131,7 +131,7 @@ public:
|
|||
|
||||
while (!TestDestroy() && parent->cur_frame < parent->end_frame) {
|
||||
int rsize = std::min(wsize, parent->end_frame - parent->cur_frame);
|
||||
parent->provider->GetAudioWithVolume(buf, parent->cur_frame,
|
||||
parent->provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(buf), parent->cur_frame,
|
||||
rsize, parent->volume);
|
||||
int written = ::write(parent->dspdev, buf, rsize * parent->bpf);
|
||||
parent->cur_frame += written / parent->bpf;
|
||||
|
@ -146,7 +146,7 @@ public:
|
|||
|
||||
void OSSPlayer::OpenStream()
|
||||
{
|
||||
bpf = provider->GetChannels() * provider->GetBytesPerSample();
|
||||
bpf = /*provider->GetChannels() * provider->GetBytesPerSample()*/sizeof(int16_t);
|
||||
|
||||
// Open device
|
||||
wxString device = to_wx(OPT_GET("Player/Audio/OSS/Device")->GetString());
|
||||
|
@ -162,14 +162,14 @@ void OSSPlayer::OpenStream()
|
|||
#endif
|
||||
|
||||
// Set number of channels
|
||||
int channels = provider->GetChannels();
|
||||
int channels = /*provider->GetChannels()*/1;
|
||||
if (ioctl(dspdev, SNDCTL_DSP_CHANNELS, &channels) < 0) {
|
||||
throw AudioPlayerOpenError("OSS player: setting channels failed");
|
||||
}
|
||||
|
||||
// Set sample format
|
||||
int sample_format;
|
||||
switch (provider->GetBytesPerSample()) {
|
||||
switch (/*provider->GetBytesPerSample()*/sizeof(int16_t)) {
|
||||
case 1:
|
||||
sample_format = AFMT_S8;
|
||||
break;
|
||||
|
|
|
@ -140,7 +140,7 @@ void PortAudioPlayer::OpenStream() {
|
|||
const PaDeviceInfo *device_info = Pa_GetDeviceInfo((*device_ids)[i]);
|
||||
PaStreamParameters pa_output_p;
|
||||
pa_output_p.device = (*device_ids)[i];
|
||||
pa_output_p.channelCount = provider->GetChannels();
|
||||
pa_output_p.channelCount = /*provider->GetChannels()*/ 1;
|
||||
pa_output_p.sampleFormat = paInt16;
|
||||
pa_output_p.suggestedLatency = device_info->defaultLowOutputLatency;
|
||||
pa_output_p.hostApiSpecificStreamInfo = nullptr;
|
||||
|
@ -222,7 +222,7 @@ int PortAudioPlayer::paCallback(const void *inputBuffer, void *outputBuffer,
|
|||
|
||||
// Play something
|
||||
if (lenAvailable > 0) {
|
||||
player->provider->GetAudioWithVolume(outputBuffer, player->current, lenAvailable, player->GetVolume());
|
||||
player->provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(outputBuffer), player->current, lenAvailable, player->GetVolume());
|
||||
|
||||
// Set play position
|
||||
player->current += lenAvailable;
|
||||
|
|
|
@ -133,11 +133,11 @@ PulseAudioPlayer::PulseAudioPlayer(agi::AudioProvider *provider) : AudioPlayer(p
|
|||
}
|
||||
|
||||
// Set up stream
|
||||
bpf = provider->GetChannels() * provider->GetBytesPerSample();
|
||||
bpf = /*provider->GetChannels() * provider->GetBytesPerSample()*/sizeof(int16_t);
|
||||
pa_sample_spec ss;
|
||||
ss.format = PA_SAMPLE_S16LE; // FIXME
|
||||
ss.rate = provider->GetSampleRate();
|
||||
ss.channels = provider->GetChannels();
|
||||
ss.channels = /*provider->GetChannels()*/1;
|
||||
pa_channel_map map;
|
||||
pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_DEFAULT);
|
||||
|
||||
|
@ -308,7 +308,7 @@ void PulseAudioPlayer::pa_stream_write(pa_stream *p, size_t length, PulseAudioPl
|
|||
unsigned long maxframes = thread->end_frame - thread->cur_frame;
|
||||
if (frames > maxframes) frames = maxframes;
|
||||
void *buf = malloc(frames * bpf);
|
||||
thread->provider->GetAudioWithVolume(buf, thread->cur_frame, frames, thread->volume);
|
||||
thread->provider->GetInt16MonoAudioWithVolume(reinterpret_cast<int16_t*>(buf), thread->cur_frame, frames, thread->volume);
|
||||
::pa_stream_write(p, buf, frames*bpf, free, 0, PA_SEEK_RELATIVE);
|
||||
thread->cur_frame += frames;
|
||||
}
|
||||
|
|
689
src/audio_player_xaudio2.cpp
Normal file
689
src/audio_player_xaudio2.cpp
Normal file
|
@ -0,0 +1,689 @@
|
|||
// Copyright (c) 2019, Qirui Wang
|
||||
// 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 the Aegisub Group 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 OWNER 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.
|
||||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
#ifdef WITH_XAUDIO2
|
||||
#include "include/aegisub/audio_player.h"
|
||||
|
||||
#include "options.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/scoped_ptr.h>
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/make_unique.h>
|
||||
|
||||
#include <xaudio2.h>
|
||||
|
||||
namespace {
|
||||
class XAudio2Thread;
|
||||
|
||||
/// @class XAudio2Player
|
||||
/// @brief XAudio2-based audio player
|
||||
///
|
||||
/// The core design idea is to have a playback thread that performs all playback operations, and use the player object as a proxy to send commands to the playback thread.
|
||||
class XAudio2Player final : public AudioPlayer {
|
||||
/// The playback thread
|
||||
std::unique_ptr<XAudio2Thread> thread;
|
||||
|
||||
/// Desired length in milliseconds to write ahead of the playback cursor
|
||||
int WantedLatency;
|
||||
|
||||
/// Multiplier for WantedLatency to get total buffer length
|
||||
int BufferLength;
|
||||
|
||||
/// @brief Tell whether playback thread is alive
|
||||
/// @return True if there is a playback thread and it's ready
|
||||
bool IsThreadAlive();
|
||||
|
||||
public:
|
||||
/// @brief Constructor
|
||||
XAudio2Player(agi::AudioProvider* provider);
|
||||
/// @brief Destructor
|
||||
~XAudio2Player() = default;
|
||||
|
||||
/// @brief Start playback
|
||||
/// @param start First audio frame to play
|
||||
/// @param count Number of audio frames to play
|
||||
void Play(int64_t start, int64_t count);
|
||||
|
||||
/// @brief Stop audio playback
|
||||
/// @param timerToo Whether to also stop the playback update timer
|
||||
void Stop();
|
||||
|
||||
/// @brief Tell whether playback is active
|
||||
/// @return True if audio is playing back
|
||||
bool IsPlaying();
|
||||
|
||||
/// @brief Get playback end position
|
||||
/// @return Audio frame index
|
||||
///
|
||||
/// Returns 0 if playback is stopped or there is no playback thread
|
||||
int64_t GetEndPosition();
|
||||
/// @brief Get approximate playback position
|
||||
/// @return Index of audio frame user is currently hearing
|
||||
///
|
||||
/// Returns 0 if playback is stopped or there is no playback thread
|
||||
int64_t GetCurrentPosition();
|
||||
|
||||
/// @brief Change playback end position
|
||||
/// @param pos New end position
|
||||
void SetEndPosition(int64_t pos);
|
||||
|
||||
/// @brief Change playback volume
|
||||
/// @param vol Amplification factor
|
||||
void SetVolume(double vol);
|
||||
};
|
||||
|
||||
/// @brief RAII support class to init and de-init the COM library
|
||||
struct COMInitialization {
|
||||
|
||||
/// Flag set if an inited COM library is managed
|
||||
bool inited = false;
|
||||
|
||||
/// @brief Destructor, de-inits COM if it is inited
|
||||
~COMInitialization() {
|
||||
if (inited) CoUninitialize();
|
||||
}
|
||||
|
||||
/// @brief Initialise the COM library as single-threaded apartment if isn't already inited by us
|
||||
bool Init() {
|
||||
if (!inited && SUCCEEDED(CoInitialize(nullptr)))
|
||||
inited = true;
|
||||
return inited;
|
||||
}
|
||||
};
|
||||
|
||||
struct ReleaseCOMObject {
|
||||
void operator()(IUnknown* obj) {
|
||||
if (obj) obj->Release();
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief RAII wrapper around Win32 HANDLE type
|
||||
struct Win32KernelHandle final : public agi::scoped_holder<HANDLE, BOOL(__stdcall*)(HANDLE)> {
|
||||
/// @brief Create with a managed handle
|
||||
/// @param handle Win32 handle to manage
|
||||
Win32KernelHandle(HANDLE handle = 0) :scoped_holder(handle, CloseHandle) {}
|
||||
|
||||
Win32KernelHandle& operator=(HANDLE new_handle) {
|
||||
scoped_holder::operator=(new_handle);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/// @class XAudio2Thread
|
||||
/// @brief Playback thread class for XAudio2Player
|
||||
///
|
||||
/// Not based on wxThread, but uses Win32 threads directly
|
||||
class XAudio2Thread :public IXAudio2VoiceCallback {
|
||||
/// @brief Win32 thread entry point
|
||||
/// @param parameter Pointer to our thread object
|
||||
/// @return Thread return value, always 0 here
|
||||
static unsigned int __stdcall ThreadProc(void* parameter);
|
||||
/// @brief Thread entry point
|
||||
void Run();
|
||||
|
||||
/// @brief Check for error state and throw exception if one occurred
|
||||
void CheckError();
|
||||
|
||||
/// Win32 handle to the thread
|
||||
Win32KernelHandle thread_handle;
|
||||
|
||||
/// Event object, world to thread, set to start playback
|
||||
Win32KernelHandle event_start_playback;
|
||||
|
||||
/// Event object, world to thread, set to stop playback
|
||||
Win32KernelHandle event_stop_playback;
|
||||
|
||||
/// Event object, world to thread, set if playback end time was updated
|
||||
Win32KernelHandle event_update_end_time;
|
||||
|
||||
/// Event object, world to thread, set if the volume was changed
|
||||
Win32KernelHandle event_set_volume;
|
||||
|
||||
/// Event object, world to thread, set if the thread should end as soon as possible
|
||||
Win32KernelHandle event_buffer_end;
|
||||
|
||||
/// Event object, world to thread, set if the thread should end as soon as possible
|
||||
Win32KernelHandle event_kill_self;
|
||||
|
||||
/// Event object, thread to world, set when the thread has entered its main loop
|
||||
Win32KernelHandle thread_running;
|
||||
|
||||
/// Event object, thread to world, set when playback is ongoing
|
||||
Win32KernelHandle is_playing;
|
||||
|
||||
/// Event object, thread to world, set if an error state has occurred (implies thread is dying)
|
||||
Win32KernelHandle error_happened;
|
||||
|
||||
/// Statically allocated error message text describing reason for error_happened being set
|
||||
const char* error_message = nullptr;
|
||||
|
||||
/// Playback volume, 1.0 is "unchanged"
|
||||
double volume = 1.0;
|
||||
|
||||
/// Audio frame to start playback at
|
||||
int64_t start_frame = 0;
|
||||
|
||||
/// Audio frame to end playback at
|
||||
int64_t end_frame = 0;
|
||||
|
||||
/// Desired length in milliseconds to write ahead of the playback cursor
|
||||
int wanted_latency;
|
||||
|
||||
/// Multiplier for WantedLatency to get total buffer length
|
||||
int buffer_length;
|
||||
|
||||
/// System millisecond timestamp of last playback start, used to calculate playback position
|
||||
ULONGLONG last_playback_restart;
|
||||
|
||||
/// Audio provider to take sample data from
|
||||
agi::AudioProvider* provider;
|
||||
|
||||
/// Buffer occupied indicator
|
||||
std::vector<bool> buffer_occupied;
|
||||
|
||||
public:
|
||||
/// @brief Constructor, creates and starts playback thread
|
||||
/// @param provider Audio provider to take sample data from
|
||||
/// @param WantedLatency Desired length in milliseconds to write ahead of the playback cursor
|
||||
/// @param BufferLength Multiplier for WantedLatency to get total buffer length
|
||||
XAudio2Thread(agi::AudioProvider* provider, int WantedLatency, int BufferLength);
|
||||
/// @brief Destructor, waits for thread to have died
|
||||
~XAudio2Thread();
|
||||
|
||||
// IXAudio2VoiceCallback
|
||||
void OnVoiceProcessingPassStart(UINT32 BytesRequired) {}
|
||||
void OnVoiceProcessingPassEnd() {}
|
||||
void OnStreamEnd() {}
|
||||
void OnBufferStart(void* pBufferContext) {}
|
||||
void OnBufferEnd(void* pBufferContext) {
|
||||
intptr_t i = reinterpret_cast<intptr_t>(pBufferContext);
|
||||
buffer_occupied[i] = false;
|
||||
SetEvent(event_buffer_end);
|
||||
}
|
||||
void OnLoopEnd(void* pBufferContext) {}
|
||||
void OnVoiceError(void* pBufferContext, HRESULT Error) {}
|
||||
|
||||
/// @brief Start audio playback
|
||||
/// @param start Audio frame to start playback at
|
||||
/// @param count Number of audio frames to play
|
||||
void Play(int64_t start, int64_t count);
|
||||
|
||||
/// @brief Stop audio playback
|
||||
void Stop();
|
||||
|
||||
/// @brief Change audio playback end point
|
||||
/// @param new_end_frame New last audio frame to play
|
||||
///
|
||||
/// Playback stops instantly if new_end_frame is before the current playback position
|
||||
void SetEndFrame(int64_t new_end_frame);
|
||||
|
||||
/// @brief Change audio playback volume
|
||||
/// @param new_volume New playback amplification factor, 1.0 is "unchanged"
|
||||
void SetVolume(double new_volume);
|
||||
|
||||
/// @brief Tell whether audio playback is active
|
||||
/// @return True if audio is being played back, false if it is not
|
||||
bool IsPlaying();
|
||||
|
||||
/// @brief Get approximate current audio frame being heard by the user
|
||||
/// @return Audio frame index
|
||||
///
|
||||
/// Returns 0 if not playing
|
||||
int64_t GetCurrentFrame();
|
||||
|
||||
/// @brief Get audio playback end point
|
||||
/// @return Audio frame index
|
||||
int64_t GetEndFrame();
|
||||
|
||||
/// @brief Tell whether playback thread has died
|
||||
/// @return True if thread is no longer running
|
||||
bool IsDead();
|
||||
};
|
||||
|
||||
unsigned int __stdcall XAudio2Thread::ThreadProc(void* parameter) {
|
||||
static_cast<XAudio2Thread*>(parameter)->Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Macro used to set error_message, error_happened and end the thread
|
||||
#define REPORT_ERROR(msg) \
|
||||
{ \
|
||||
ResetEvent(is_playing); \
|
||||
error_message = "XAudio2Thread: " msg; \
|
||||
SetEvent(error_happened); \
|
||||
return; \
|
||||
}
|
||||
|
||||
void XAudio2Thread::Run() {
|
||||
COMInitialization COM_library;
|
||||
if (!COM_library.Init()) {
|
||||
REPORT_ERROR("Could not initialise COM")
|
||||
}
|
||||
IXAudio2* pXAudio2;
|
||||
IXAudio2SourceVoice* pSourceVoice;
|
||||
HRESULT hr;
|
||||
if (FAILED(hr = XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR))) {
|
||||
REPORT_ERROR("Failed initializing XAudio2")
|
||||
}
|
||||
IXAudio2MasteringVoice* pMasterVoice = NULL;
|
||||
if (FAILED(hr = pXAudio2->CreateMasteringVoice(&pMasterVoice))) {
|
||||
REPORT_ERROR("Failed initializing XAudio2 MasteringVoice")
|
||||
}
|
||||
|
||||
// Describe the wave format
|
||||
WAVEFORMATEX wfx;
|
||||
wfx.nSamplesPerSec = provider->GetSampleRate();
|
||||
wfx.cbSize = 0;
|
||||
bool original = true;
|
||||
wfx.wFormatTag = provider->AreSamplesFloat() ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
|
||||
wfx.nChannels = provider->GetChannels();
|
||||
wfx.wBitsPerSample = provider->GetBytesPerSample() * 8;
|
||||
wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
|
||||
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||||
|
||||
if (FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &wfx, 0, 2, this))) {
|
||||
if (hr == XAUDIO2_E_INVALID_CALL) {
|
||||
// Retry with 16bit mono
|
||||
original = false;
|
||||
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||||
wfx.nChannels = 1;
|
||||
wfx.wBitsPerSample = sizeof(int16_t) * 8;
|
||||
wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
|
||||
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
|
||||
if (FAILED(hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &wfx, 0, 2, this))) {
|
||||
REPORT_ERROR("Failed initializing XAudio2 SourceVoice")
|
||||
}
|
||||
}
|
||||
else {
|
||||
REPORT_ERROR("Failed initializing XAudio2 SourceVoice")
|
||||
}
|
||||
}
|
||||
|
||||
// Now we're ready to roll!
|
||||
SetEvent(thread_running);
|
||||
bool running = true;
|
||||
|
||||
HANDLE events_to_wait[] = {
|
||||
event_start_playback,
|
||||
event_stop_playback,
|
||||
event_update_end_time,
|
||||
event_set_volume,
|
||||
event_buffer_end,
|
||||
event_kill_self
|
||||
};
|
||||
|
||||
int64_t next_input_frame = 0;
|
||||
DWORD buffer_offset = 0;
|
||||
bool playback_should_be_running = false;
|
||||
int current_latency = wanted_latency;
|
||||
const int wanted_frames = wanted_latency * wfx.nSamplesPerSec / 1000;
|
||||
const DWORD wanted_latency_bytes = wanted_frames * wfx.nBlockAlign;
|
||||
std::vector<std::vector<BYTE> > buff(buffer_length);
|
||||
for (auto& i : buff)
|
||||
i.resize(wanted_latency_bytes);
|
||||
|
||||
while (running) {
|
||||
DWORD wait_result = WaitForMultipleObjects(sizeof(events_to_wait) / sizeof(HANDLE), events_to_wait, FALSE, INFINITE);
|
||||
|
||||
switch (wait_result) {
|
||||
case WAIT_OBJECT_0 + 0:
|
||||
// Start or restart playback
|
||||
pSourceVoice->Stop();
|
||||
pSourceVoice->FlushSourceBuffers();
|
||||
|
||||
next_input_frame = start_frame;
|
||||
playback_should_be_running = true;
|
||||
pSourceVoice->Start();
|
||||
SetEvent(is_playing);
|
||||
goto do_fill_buffer;
|
||||
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
stop_playback:
|
||||
// Stop playing
|
||||
ResetEvent(is_playing);
|
||||
pSourceVoice->Stop();
|
||||
pSourceVoice->FlushSourceBuffers();
|
||||
playback_should_be_running = false;
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0 + 2:
|
||||
// Set end frame
|
||||
if (end_frame <= next_input_frame)
|
||||
goto stop_playback;
|
||||
goto do_fill_buffer;
|
||||
|
||||
case WAIT_OBJECT_0 + 3:
|
||||
// Change volume
|
||||
pSourceVoice->SetVolume(volume);
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0 + 4:
|
||||
// Buffer end
|
||||
do_fill_buffer:
|
||||
{
|
||||
// Time to fill more into buffer
|
||||
if (!playback_should_be_running)
|
||||
break;
|
||||
|
||||
for (int i = 0; i < buffer_length; ++i) {
|
||||
if (!buffer_occupied[i]) {
|
||||
int fill_len = std::min<int>(end_frame - next_input_frame, wanted_frames);
|
||||
if (fill_len <= 0)
|
||||
break;
|
||||
buffer_occupied[i] = true;
|
||||
if (original)
|
||||
provider->GetAudio(buff[i].data(), next_input_frame, fill_len);
|
||||
else
|
||||
provider->GetInt16MonoAudio(reinterpret_cast<int16_t*>(buff[i].data()), next_input_frame, fill_len);
|
||||
next_input_frame += fill_len;
|
||||
XAUDIO2_BUFFER xbf;
|
||||
xbf.Flags = fill_len + next_input_frame == end_frame ? XAUDIO2_END_OF_STREAM : 0;
|
||||
xbf.AudioBytes = fill_len * wfx.nBlockAlign;
|
||||
xbf.pAudioData = buff[i].data();
|
||||
xbf.PlayBegin = 0;
|
||||
xbf.PlayLength = 0;
|
||||
xbf.LoopBegin = 0;
|
||||
xbf.LoopLength = 0;
|
||||
xbf.LoopCount = 0;
|
||||
xbf.pContext = reinterpret_cast<void*>(static_cast<intptr_t>(i));
|
||||
if (FAILED(hr = pSourceVoice->SubmitSourceBuffer(&xbf))) {
|
||||
REPORT_ERROR("Failed initializing Submit Buffer")
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0 + 5:
|
||||
// Perform suicide
|
||||
running = false;
|
||||
goto stop_playback;
|
||||
}
|
||||
|
||||
default:
|
||||
REPORT_ERROR("Something bad happened while waiting on events in playback loop, either the wait failed or an event object was abandoned.")
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef REPORT_ERROR
|
||||
|
||||
void XAudio2Thread::CheckError()
|
||||
{
|
||||
try {
|
||||
switch (WaitForSingleObject(error_happened, 0))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
throw error_message;
|
||||
|
||||
case WAIT_ABANDONED:
|
||||
throw "The XAudio2Thread error signal event was abandoned, somehow. This should not happen.";
|
||||
|
||||
case WAIT_FAILED:
|
||||
throw "Failed checking state of XAudio2Thread error signal event.";
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
ResetEvent(is_playing);
|
||||
ResetEvent(thread_running);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
XAudio2Thread::XAudio2Thread(agi::AudioProvider* provider, int WantedLatency, int BufferLength)
|
||||
: event_start_playback(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, event_stop_playback(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, event_update_end_time(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, event_set_volume(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, event_buffer_end(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, event_kill_self(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, thread_running(CreateEvent(0, TRUE, FALSE, 0))
|
||||
, is_playing(CreateEvent(0, TRUE, FALSE, 0))
|
||||
, error_happened(CreateEvent(0, FALSE, FALSE, 0))
|
||||
, wanted_latency(WantedLatency)
|
||||
, buffer_length(BufferLength < XAUDIO2_MAX_QUEUED_BUFFERS ? BufferLength : XAUDIO2_MAX_QUEUED_BUFFERS)
|
||||
, provider(provider)
|
||||
, buffer_occupied(BufferLength)
|
||||
{
|
||||
if (!(thread_handle = (HANDLE)_beginthreadex(0, 0, ThreadProc, this, 0, 0))) {
|
||||
throw AudioPlayerOpenError("Failed creating playback thread in XAudio2Player. This is bad.");
|
||||
}
|
||||
|
||||
HANDLE running_or_error[] = { thread_running, error_happened };
|
||||
switch (WaitForMultipleObjects(2, running_or_error, FALSE, INFINITE)) {
|
||||
case WAIT_OBJECT_0:
|
||||
// running, all good
|
||||
return;
|
||||
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
// error happened, we fail
|
||||
throw AudioPlayerOpenError(error_message ? error_message : "Failed wait for thread start or thread error in XAudio2Player. This is bad.");
|
||||
|
||||
default:
|
||||
throw AudioPlayerOpenError("Failed wait for thread start or thread error in XAudio2Player. This is bad.");
|
||||
}
|
||||
}
|
||||
|
||||
XAudio2Thread::~XAudio2Thread() {
|
||||
SetEvent(event_kill_self);
|
||||
WaitForSingleObject(thread_handle, INFINITE);
|
||||
}
|
||||
|
||||
void XAudio2Thread::Play(int64_t start, int64_t count)
|
||||
{
|
||||
CheckError();
|
||||
|
||||
start_frame = start;
|
||||
end_frame = start + count;
|
||||
SetEvent(event_start_playback);
|
||||
|
||||
last_playback_restart = GetTickCount64();
|
||||
|
||||
// Block until playback actually begins to avoid race conditions with
|
||||
// checking if playback is in progress
|
||||
HANDLE events_to_wait[] = { is_playing, error_happened };
|
||||
switch (WaitForMultipleObjects(2, events_to_wait, FALSE, INFINITE)) {
|
||||
case WAIT_OBJECT_0 + 0: // Playing
|
||||
LOG_D("audio/player/xaudio2") << "Playback begun";
|
||||
break;
|
||||
case WAIT_OBJECT_0 + 1: // Error
|
||||
throw error_message;
|
||||
default:
|
||||
throw agi::InternalError("Unexpected result from WaitForMultipleObjects in XAudio2Thread::Play");
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Thread::Stop() {
|
||||
CheckError();
|
||||
|
||||
SetEvent(event_stop_playback);
|
||||
}
|
||||
|
||||
void XAudio2Thread::SetEndFrame(int64_t new_end_frame) {
|
||||
CheckError();
|
||||
|
||||
end_frame = new_end_frame;
|
||||
SetEvent(event_update_end_time);
|
||||
}
|
||||
|
||||
void XAudio2Thread::SetVolume(double new_volume) {
|
||||
CheckError();
|
||||
|
||||
volume = new_volume;
|
||||
SetEvent(event_set_volume);
|
||||
}
|
||||
|
||||
bool XAudio2Thread::IsPlaying() {
|
||||
CheckError();
|
||||
|
||||
switch (WaitForSingleObject(is_playing, 0))
|
||||
{
|
||||
case WAIT_ABANDONED:
|
||||
throw "The XAudio2Thread playback state event was abandoned, somehow. This should not happen.";
|
||||
|
||||
case WAIT_FAILED:
|
||||
throw "Failed checking state of XAudio2Thread playback state event.";
|
||||
|
||||
case WAIT_OBJECT_0:
|
||||
return true;
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t XAudio2Thread::GetCurrentFrame() {
|
||||
CheckError();
|
||||
if (!IsPlaying()) return 0;
|
||||
ULONGLONG milliseconds_elapsed = GetTickCount64() - last_playback_restart;
|
||||
return start_frame + milliseconds_elapsed * provider->GetSampleRate() / 1000;
|
||||
}
|
||||
|
||||
int64_t XAudio2Thread::GetEndFrame() {
|
||||
CheckError();
|
||||
return end_frame;
|
||||
}
|
||||
|
||||
bool XAudio2Thread::IsDead() {
|
||||
switch (WaitForSingleObject(thread_running, 0))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
XAudio2Player::XAudio2Player(agi::AudioProvider* provider) :AudioPlayer(provider) {
|
||||
// The buffer will hold BufferLength times WantedLatency milliseconds of audio
|
||||
WantedLatency = OPT_GET("Player/Audio/DirectSound/Buffer Latency")->GetInt();
|
||||
BufferLength = OPT_GET("Player/Audio/DirectSound/Buffer Length")->GetInt();
|
||||
|
||||
// sanity checking
|
||||
if (WantedLatency <= 0)
|
||||
WantedLatency = 100;
|
||||
if (BufferLength <= 0)
|
||||
BufferLength = 5;
|
||||
|
||||
try {
|
||||
thread = agi::make_unique<XAudio2Thread>(provider, WantedLatency, BufferLength);
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
throw AudioPlayerOpenError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
bool XAudio2Player::IsThreadAlive() {
|
||||
if (thread && thread->IsDead())
|
||||
thread.reset();
|
||||
return static_cast<bool>(thread);
|
||||
}
|
||||
|
||||
void XAudio2Player::Play(int64_t start, int64_t count) {
|
||||
try {
|
||||
thread->Play(start, count);
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Player::Stop() {
|
||||
try {
|
||||
if (IsThreadAlive()) thread->Stop();
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
}
|
||||
}
|
||||
|
||||
bool XAudio2Player::IsPlaying() {
|
||||
try {
|
||||
if (!IsThreadAlive()) return false;
|
||||
return thread->IsPlaying();
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t XAudio2Player::GetEndPosition() {
|
||||
try {
|
||||
if (!IsThreadAlive()) return 0;
|
||||
return thread->GetEndFrame();
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t XAudio2Player::GetCurrentPosition() {
|
||||
try {
|
||||
if (!IsThreadAlive()) return 0;
|
||||
return thread->GetCurrentFrame();
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Player::SetEndPosition(int64_t pos) {
|
||||
try {
|
||||
if (IsThreadAlive()) thread->SetEndFrame(pos);
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
}
|
||||
}
|
||||
|
||||
void XAudio2Player::SetVolume(double vol) {
|
||||
try {
|
||||
if (IsThreadAlive()) thread->SetVolume(vol);
|
||||
}
|
||||
catch (const char* msg) {
|
||||
LOG_E("audio/player/xaudio2") << msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioPlayer> CreateXAudio2Player(agi::AudioProvider* provider, wxWindow*) {
|
||||
return agi::make_unique<XAudio2Player>(provider);
|
||||
}
|
||||
|
||||
#endif // WITH_DIRECTSOUND
|
|
@ -107,12 +107,14 @@ void AvisynthAudioProvider::LoadFromClip(AVSValue clip) {
|
|||
if (!vi.HasAudio()) throw agi::AudioDataNotFound("No audio found.");
|
||||
|
||||
IScriptEnvironment *env = avs_wrapper.GetEnv();
|
||||
AVSValue script;
|
||||
|
||||
// Convert to one channel
|
||||
AVSValue script = env->Invoke(OPT_GET("Audio/Downmixer")->GetString().c_str(), clip);
|
||||
if (OPT_GET("Audio/Downmixer")->GetString() != "None")
|
||||
script = env->Invoke(OPT_GET("Audio/Downmixer")->GetString().c_str(), clip);
|
||||
else
|
||||
script = clip;
|
||||
|
||||
// Convert to 16 bits per sample
|
||||
script = env->Invoke("ConvertAudioTo16bit", script);
|
||||
vi = script.AsClip()->GetVideoInfo();
|
||||
|
||||
// Convert sample rate
|
||||
|
@ -132,8 +134,8 @@ void AvisynthAudioProvider::LoadFromClip(AVSValue clip) {
|
|||
channels = vi.AudioChannels();
|
||||
decoded_samples = num_samples = vi.num_audio_samples;
|
||||
sample_rate = vi.SamplesPerSecond();
|
||||
bytes_per_sample = vi.BytesPerAudioSample();
|
||||
float_samples = false;
|
||||
bytes_per_sample = vi.BytesPerChannelSample();
|
||||
float_samples = vi.IsSampleType(SAMPLE_FLOAT);
|
||||
|
||||
this->clip = tempclip;
|
||||
}
|
||||
|
|
|
@ -144,7 +144,7 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) {
|
|||
// update access time of index file so it won't get cleaned away
|
||||
agi::fs::Touch(CacheName);
|
||||
|
||||
AudioSource = FFMS_CreateAudioSource(filename.string().c_str(), TrackNumber, Index, -1, &ErrInfo);
|
||||
AudioSource = FFMS_CreateAudioSource(filename.string().c_str(), TrackNumber, Index, FFMS_DELAY_FIRST_VIDEO_TRACK, &ErrInfo);
|
||||
if (!AudioSource)
|
||||
throw agi::AudioProviderError(std::string("Failed to open audio track: ") + ErrInfo.Buffer);
|
||||
|
||||
|
@ -168,7 +168,8 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) {
|
|||
}
|
||||
|
||||
#if FFMS_VERSION >= ((2 << 24) | (17 << 16) | (4 << 8) | 0)
|
||||
if (channels > 1 || bytes_per_sample != 2) {
|
||||
if (OPT_GET("Provider/Audio/FFmpegSource/Downmix")->GetBool()) {
|
||||
if (channels > 1 || bytes_per_sample != 2 || float_samples) {
|
||||
std::unique_ptr<FFMS_ResampleOptions, decltype(&FFMS_DestroyResampleOptions)>
|
||||
opt(FFMS_CreateResampleOptions(AudioSource), FFMS_DestroyResampleOptions);
|
||||
opt->ChannelLayout = FFMS_CH_FRONT_CENTER;
|
||||
|
@ -181,6 +182,7 @@ void FFmpegSourceAudioProvider::LoadAudio(agi::fs::path const& filename) {
|
|||
float_samples = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ void AudioSpectrumRenderer::RecreateCache()
|
|||
|
||||
if (provider)
|
||||
{
|
||||
size_t block_count = (size_t)((provider->GetNumSamples() + (size_t)(1<<derivation_dist) - 1) >> derivation_dist);
|
||||
size_t block_count = (size_t)((provider->GetNumSamples() + ((size_t)1<<derivation_dist) - 1) >> derivation_dist);
|
||||
cache = agi::make_unique<AudioSpectrumCache>(block_count, this);
|
||||
|
||||
#ifdef WITH_FFTW3
|
||||
|
@ -170,8 +170,8 @@ void AudioSpectrumRenderer::FillBlock(size_t block_index, float *block)
|
|||
assert(cache);
|
||||
assert(block);
|
||||
|
||||
int64_t first_sample = ((int64_t)block_index) << derivation_dist;
|
||||
provider->GetAudio(&audio_scratch[0], first_sample, 2 << derivation_size);
|
||||
int64_t first_sample = (((int64_t)block_index) << derivation_dist) - ((int64_t)1 << derivation_size);
|
||||
provider->GetInt16MonoAudio(audio_scratch.data(), first_sample, 2 << derivation_size);
|
||||
|
||||
#ifdef WITH_FFTW3
|
||||
ConvertToFloat(2 << derivation_size, dft_input);
|
||||
|
@ -181,7 +181,7 @@ void AudioSpectrumRenderer::FillBlock(size_t block_index, float *block)
|
|||
double scale_factor = 9 / sqrt(2 << (derivation_size + 1));
|
||||
|
||||
fftw_complex *o = dft_output;
|
||||
for (size_t si = 1<<derivation_size; si > 0; --si)
|
||||
for (size_t si = (size_t)1<<derivation_size; si > 0; --si)
|
||||
{
|
||||
*block++ = log10( sqrt(o[0][0] * o[0][0] + o[0][1] * o[0][1]) * scale_factor + 1 );
|
||||
o++;
|
||||
|
|
|
@ -80,15 +80,12 @@ void AudioWaveformRenderer::Render(wxBitmap &bmp, int start, AudioRenderingStyle
|
|||
|
||||
double cur_sample = start * pixel_samples;
|
||||
|
||||
assert(provider->GetBytesPerSample() == 2);
|
||||
assert(provider->GetChannels() == 1);
|
||||
|
||||
wxPen pen_peaks(wxPen(pal->get(0.4f)));
|
||||
wxPen pen_avgs(wxPen(pal->get(0.7f)));
|
||||
|
||||
for (int x = 0; x < rect.width; ++x)
|
||||
{
|
||||
provider->GetAudio(audio_buffer.get(), (int64_t)cur_sample, (int64_t)pixel_samples);
|
||||
provider->GetInt16MonoAudio(reinterpret_cast<int16_t*>(audio_buffer.get()), (int64_t)cur_sample, (int64_t)pixel_samples);
|
||||
cur_sample += pixel_samples;
|
||||
|
||||
int peak_min = 0, peak_max = 0;
|
||||
|
|
|
@ -390,7 +390,7 @@ int AudioTimingControllerKaraoke::MoveMarker(KaraokeMarker *marker, int new_posi
|
|||
void AudioTimingControllerKaraoke::AnnounceChanges(int syl) {
|
||||
if (syl < 0) return;
|
||||
|
||||
if (syl == cur_syl || syl == cur_syl + 1) {
|
||||
if (static_cast<unsigned>(syl) == cur_syl || static_cast<unsigned>(syl) == cur_syl + 1) {
|
||||
AnnounceUpdatedPrimaryRange();
|
||||
AnnounceUpdatedStyleRanges();
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "audio_timing.h"
|
||||
#include "command/command.h"
|
||||
#include "compat.h"
|
||||
#include "frame_main.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
|
@ -52,6 +53,7 @@
|
|||
#include "video_controller.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <libaegisub/dispatch.h>
|
||||
#include <libaegisub/format.h>
|
||||
#include <libaegisub/lua/ffi.h>
|
||||
#include <libaegisub/lua/modules.h>
|
||||
|
@ -270,6 +272,19 @@ namespace {
|
|||
return 2;
|
||||
}
|
||||
|
||||
int lua_set_status_text(lua_State *L)
|
||||
{
|
||||
const agi::Context *c = get_context(L);
|
||||
if (!c || !c->frame) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
std::string text = check_string(L, 1);
|
||||
lua_pop(L, 1);
|
||||
agi::dispatch::Main().Async([=] { c->frame->StatusTimeout(to_wx(text)); });
|
||||
return 0;
|
||||
}
|
||||
|
||||
int project_properties(lua_State *L)
|
||||
{
|
||||
const agi::Context *c = get_context(L);
|
||||
|
@ -473,6 +488,7 @@ namespace {
|
|||
set_field<get_translation>(L, "gettext");
|
||||
set_field<project_properties>(L, "project_properties");
|
||||
set_field<lua_get_audio_selection>(L, "get_audio_selection");
|
||||
set_field<lua_set_status_text>(L, "set_status_text");
|
||||
|
||||
// store aegisub table to globals
|
||||
lua_settable(L, LUA_GLOBALSINDEX);
|
||||
|
|
|
@ -744,8 +744,8 @@ namespace Automation4 {
|
|||
, can_modify(can_modify)
|
||||
, can_set_undo(can_set_undo)
|
||||
{
|
||||
for (auto& line : ass->Info)
|
||||
lines.push_back(nullptr);
|
||||
// for (auto& line : ass->Info) lines.push_back(nullptr);
|
||||
lines.insert(lines.end(), ass->Info.size(), nullptr);
|
||||
for (auto& line : ass->Styles)
|
||||
lines.push_back(&line);
|
||||
for (auto& line : ass->Events)
|
||||
|
|
751
src/avisynth.h
751
src/avisynth.h
|
@ -1,751 +0,0 @@
|
|||
// Avisynth v2.5. Copyright 2002 Ben Rudiak-Gould et al.
|
||||
// http://www.avisynth.org
|
||||
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or visit
|
||||
// http://www.gnu.org/copyleft/gpl.html .
|
||||
//
|
||||
// Linking Avisynth statically or dynamically with other modules is making a
|
||||
// combined work based on Avisynth. Thus, the terms and conditions of the GNU
|
||||
// General Public License cover the whole combination.
|
||||
//
|
||||
// As a special exception, the copyright holders of Avisynth give you
|
||||
// permission to link Avisynth with independent modules that communicate with
|
||||
// Avisynth solely through the interfaces defined in avisynth.h, regardless of the license
|
||||
// terms of these independent modules, and to copy and distribute the
|
||||
// resulting combined work under terms of your choice, provided that
|
||||
// every copy of the combined work is accompanied by a complete copy of
|
||||
// the source code of Avisynth (the version of Avisynth used to produce the
|
||||
// combined work), being distributed under the terms of the GNU General
|
||||
// Public License plus this exception. An independent module is a module
|
||||
// which is not derived from or based on Avisynth, such as 3rd-party filters,
|
||||
// import and export plugins, or graphical user interfaces.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#ifndef __AVISYNTH_H__
|
||||
#define __AVISYNTH_H__
|
||||
|
||||
enum { AVISYNTH_INTERFACE_VERSION = 3 };
|
||||
|
||||
|
||||
/* Define all types necessary for interfacing with avisynth.dll
|
||||
Moved from internal.h */
|
||||
|
||||
// Win32 API macros, notably the types BYTE, DWORD, ULONG, etc.
|
||||
#include <windef.h>
|
||||
|
||||
// COM interface macros
|
||||
#include <objbase.h>
|
||||
|
||||
|
||||
// Raster types used by VirtualDub & Avisynth
|
||||
#define in64 (__int64)(unsigned short)
|
||||
typedef unsigned long Pixel; // this will break on 64-bit machines!
|
||||
typedef unsigned long Pixel32;
|
||||
typedef unsigned char Pixel8;
|
||||
typedef long PixCoord;
|
||||
typedef long PixDim;
|
||||
typedef long PixOffset;
|
||||
|
||||
|
||||
/* Compiler-specific crap */
|
||||
|
||||
// Tell MSVC to stop precompiling here
|
||||
#ifdef _MSC_VER
|
||||
#pragma hdrstop
|
||||
#endif
|
||||
|
||||
// Set up debugging macros for MS compilers; for others, step down to the
|
||||
// standard <assert.h> interface
|
||||
#ifdef _MSC_VER
|
||||
#include <crtdbg.h>
|
||||
#else
|
||||
#define _RPT0(a,b) ((void)0)
|
||||
#define _RPT1(a,b,c) ((void)0)
|
||||
#define _RPT2(a,b,c,d) ((void)0)
|
||||
#define _RPT3(a,b,c,d,e) ((void)0)
|
||||
#define _RPT4(a,b,c,d,e,f) ((void)0)
|
||||
|
||||
#define _ASSERTE(x) assert(x)
|
||||
#include <assert.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// I had problems with Premiere wanting 1-byte alignment for its structures,
|
||||
// so I now set the Avisynth struct alignment explicitly here.
|
||||
#pragma pack(push,8)
|
||||
|
||||
#define FRAME_ALIGN 16
|
||||
// Default frame alignment is 16 bytes, to help P4, when using SSE2
|
||||
|
||||
// The VideoInfo struct holds global information about a clip (i.e.
|
||||
// information that does not depend on the frame number). The GetVideoInfo
|
||||
// method in IClip returns this struct.
|
||||
|
||||
// Audio Sample information
|
||||
typedef float SFLOAT;
|
||||
|
||||
enum {SAMPLE_INT8 = 1<<0,
|
||||
SAMPLE_INT16 = 1<<1,
|
||||
SAMPLE_INT24 = 1<<2, // Int24 is a very stupid thing to code, but it's supported by some hardware.
|
||||
SAMPLE_INT32 = 1<<3,
|
||||
SAMPLE_FLOAT = 1<<4};
|
||||
|
||||
enum {
|
||||
PLANAR_Y=1<<0,
|
||||
PLANAR_U=1<<1,
|
||||
PLANAR_V=1<<2,
|
||||
PLANAR_ALIGNED=1<<3,
|
||||
PLANAR_Y_ALIGNED=PLANAR_Y|PLANAR_ALIGNED,
|
||||
PLANAR_U_ALIGNED=PLANAR_U|PLANAR_ALIGNED,
|
||||
PLANAR_V_ALIGNED=PLANAR_V|PLANAR_ALIGNED,
|
||||
};
|
||||
|
||||
struct VideoInfo {
|
||||
int width, height; // width=0 means no video
|
||||
unsigned fps_numerator, fps_denominator;
|
||||
int num_frames;
|
||||
// This is more extensible than previous versions. More properties can be added seeminglesly.
|
||||
|
||||
// Colorspace properties.
|
||||
enum {
|
||||
CS_BGR = 1<<28,
|
||||
CS_YUV = 1<<29,
|
||||
CS_INTERLEAVED = 1<<30,
|
||||
CS_PLANAR = 1<<31
|
||||
};
|
||||
|
||||
// Specific colorformats
|
||||
enum { CS_UNKNOWN = 0,
|
||||
CS_BGR24 = 1<<0 | CS_BGR | CS_INTERLEAVED,
|
||||
CS_BGR32 = 1<<1 | CS_BGR | CS_INTERLEAVED,
|
||||
CS_YUY2 = 1<<2 | CS_YUV | CS_INTERLEAVED,
|
||||
CS_YV12 = 1<<3 | CS_YUV | CS_PLANAR, // y-v-u, planar
|
||||
CS_I420 = 1<<4 | CS_YUV | CS_PLANAR, // y-u-v, planar
|
||||
CS_IYUV = 1<<4 | CS_YUV | CS_PLANAR // same as above
|
||||
};
|
||||
int pixel_type; // changed to int as of 2.5
|
||||
|
||||
|
||||
int audio_samples_per_second; // 0 means no audio
|
||||
int sample_type; // as of 2.5
|
||||
__int64 num_audio_samples; // changed as of 2.5
|
||||
int nchannels; // as of 2.5
|
||||
|
||||
// Imagetype properties
|
||||
|
||||
int image_type;
|
||||
|
||||
enum {
|
||||
IT_BFF = 1<<0,
|
||||
IT_TFF = 1<<1,
|
||||
IT_FIELDBASED = 1<<2
|
||||
};
|
||||
|
||||
// useful functions of the above
|
||||
bool HasVideo() const { return (width!=0); }
|
||||
bool HasAudio() const { return (audio_samples_per_second!=0); }
|
||||
bool IsRGB() const { return !!(pixel_type&CS_BGR); }
|
||||
bool IsRGB24() const { return (pixel_type&CS_BGR24)==CS_BGR24; } // Clear out additional properties
|
||||
bool IsRGB32() const { return (pixel_type & CS_BGR32) == CS_BGR32 ; }
|
||||
bool IsYUV() const { return !!(pixel_type&CS_YUV ); }
|
||||
bool IsYUY2() const { return (pixel_type & CS_YUY2) == CS_YUY2; }
|
||||
bool IsYV12() const { return ((pixel_type & CS_YV12) == CS_YV12)||((pixel_type & CS_I420) == CS_I420); }
|
||||
bool IsColorSpace(int c_space) const { return ((pixel_type & c_space) == c_space); }
|
||||
bool Is(int property) const { return ((pixel_type & property)==property ); }
|
||||
bool IsPlanar() const { return !!(pixel_type & CS_PLANAR); }
|
||||
bool IsFieldBased() const { return !!(image_type & IT_FIELDBASED); }
|
||||
bool IsParityKnown() const { return ((image_type & IT_FIELDBASED)&&(image_type & (IT_BFF|IT_TFF))); }
|
||||
bool IsBFF() const { return !!(image_type & IT_BFF); }
|
||||
bool IsTFF() const { return !!(image_type & IT_TFF); }
|
||||
|
||||
bool IsVPlaneFirst() const {return ((pixel_type & CS_YV12) == CS_YV12); } // Don't use this
|
||||
int BytesFromPixels(int pixels) const { return pixels * (BitsPerPixel()>>3); } // Will not work on planar images, but will return only luma planes
|
||||
int RowSize() const { return BytesFromPixels(width); } // Also only returns first plane on planar images
|
||||
int BMPSize() const { if (IsPlanar()) {int p = height * ((RowSize()+3) & ~3); p+=p>>1; return p; } return height * ((RowSize()+3) & ~3); }
|
||||
__int64 AudioSamplesFromFrames(__int64 frames) const { return (fps_numerator && HasVideo()) ? ((__int64)(frames) * audio_samples_per_second * fps_denominator / fps_numerator) : 0; }
|
||||
int FramesFromAudioSamples(__int64 samples) const { return (fps_denominator && HasAudio()) ? (int)((samples * (__int64)fps_numerator)/((__int64)fps_denominator * (__int64)audio_samples_per_second)) : 0; }
|
||||
__int64 AudioSamplesFromBytes(__int64 bytes) const { return HasAudio() ? bytes / BytesPerAudioSample() : 0; }
|
||||
__int64 BytesFromAudioSamples(__int64 samples) const { return samples * BytesPerAudioSample(); }
|
||||
int AudioChannels() const { return nchannels; }
|
||||
int SampleType() const{ return sample_type;}
|
||||
bool IsSampleType(int testtype) const{ return !!(sample_type&testtype);}
|
||||
int SamplesPerSecond() const { return audio_samples_per_second; }
|
||||
int BytesPerAudioSample() const { return nchannels*BytesPerChannelSample();}
|
||||
void SetFieldBased(bool isfieldbased) { if (isfieldbased) image_type|=IT_FIELDBASED; else image_type&=~IT_FIELDBASED; }
|
||||
void Set(int property) { image_type|=property; }
|
||||
void Clear(int property) { image_type&=~property; }
|
||||
|
||||
int BitsPerPixel() const {
|
||||
switch (pixel_type) {
|
||||
case CS_BGR24:
|
||||
return 24;
|
||||
case CS_BGR32:
|
||||
return 32;
|
||||
case CS_YUY2:
|
||||
return 16;
|
||||
case CS_YV12:
|
||||
case CS_I420:
|
||||
return 12;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
int BytesPerChannelSample() const {
|
||||
switch (sample_type) {
|
||||
case SAMPLE_INT8:
|
||||
return sizeof(signed char);
|
||||
case SAMPLE_INT16:
|
||||
return sizeof(signed short);
|
||||
case SAMPLE_INT24:
|
||||
return 3;
|
||||
case SAMPLE_INT32:
|
||||
return sizeof(signed int);
|
||||
case SAMPLE_FLOAT:
|
||||
return sizeof(SFLOAT);
|
||||
default:
|
||||
_ASSERTE("Sample type not recognized!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// useful mutator
|
||||
void SetFPS(unsigned numerator, unsigned denominator) {
|
||||
if ((numerator == 0) || (denominator == 0)) {
|
||||
fps_numerator = 0;
|
||||
fps_denominator = 1;
|
||||
}
|
||||
else {
|
||||
unsigned x=numerator, y=denominator;
|
||||
while (y) { // find gcd
|
||||
unsigned t = x%y; x = y; y = t;
|
||||
}
|
||||
fps_numerator = numerator/x;
|
||||
fps_denominator = denominator/x;
|
||||
}
|
||||
}
|
||||
|
||||
// Range protected multiply-divide of FPS
|
||||
void MulDivFPS(unsigned multiplier, unsigned divisor) {
|
||||
unsigned __int64 numerator = UInt32x32To64(fps_numerator, multiplier);
|
||||
unsigned __int64 denominator = UInt32x32To64(fps_denominator, divisor);
|
||||
|
||||
unsigned __int64 x=numerator, y=denominator;
|
||||
while (y) { // find gcd
|
||||
unsigned __int64 t = x%y; x = y; y = t;
|
||||
}
|
||||
numerator /= x; // normalize
|
||||
denominator /= x;
|
||||
|
||||
unsigned __int64 temp = numerator | denominator; // Just looking top bit
|
||||
unsigned u = 0;
|
||||
while (temp & 0xffffffff80000000) { // or perhaps > 16777216*2
|
||||
temp = Int64ShrlMod32(temp, 1);
|
||||
u++;
|
||||
}
|
||||
if (u) { // Scale to fit
|
||||
const unsigned round = 1 << (u-1);
|
||||
SetFPS( (unsigned)Int64ShrlMod32(numerator + round, u),
|
||||
(unsigned)Int64ShrlMod32(denominator + round, u) );
|
||||
}
|
||||
else {
|
||||
fps_numerator = (unsigned)numerator;
|
||||
fps_denominator = (unsigned)denominator;
|
||||
}
|
||||
}
|
||||
|
||||
// Test for same colorspace
|
||||
bool IsSameColorspace(const VideoInfo& vi) const {
|
||||
if (vi.pixel_type == pixel_type) return TRUE;
|
||||
if (IsYV12() && vi.IsYV12()) return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// VideoFrameBuffer holds information about a memory block which is used
|
||||
// for video data. For efficiency, instances of this class are not deleted
|
||||
// when the refcount reaches zero; instead they're stored in a linked list
|
||||
// to be reused. The instances are deleted when the corresponding AVS
|
||||
// file is closed.
|
||||
|
||||
class VideoFrameBuffer {
|
||||
BYTE* const data;
|
||||
const int data_size;
|
||||
// sequence_number is incremented every time the buffer is changed, so
|
||||
// that stale views can tell they're no longer valid.
|
||||
long sequence_number;
|
||||
|
||||
friend class VideoFrame;
|
||||
friend class Cache;
|
||||
friend class ScriptEnvironment;
|
||||
long refcount;
|
||||
|
||||
public:
|
||||
VideoFrameBuffer(int size);
|
||||
VideoFrameBuffer();
|
||||
~VideoFrameBuffer();
|
||||
|
||||
const BYTE* GetReadPtr() const { return data; }
|
||||
BYTE* GetWritePtr() { ++sequence_number; return data; }
|
||||
int GetDataSize() { return data_size; }
|
||||
int GetSequenceNumber() { return sequence_number; }
|
||||
int GetRefcount() { return refcount; }
|
||||
};
|
||||
|
||||
|
||||
class IClip;
|
||||
class PClip;
|
||||
class PVideoFrame;
|
||||
class IScriptEnvironment;
|
||||
class AVSValue;
|
||||
|
||||
|
||||
// VideoFrame holds a "window" into a VideoFrameBuffer. Operator new
|
||||
// is overloaded to recycle class instances.
|
||||
|
||||
class VideoFrame {
|
||||
int refcount;
|
||||
VideoFrameBuffer* const vfb;
|
||||
const int offset, pitch, row_size, height, offsetU, offsetV, pitchUV; // U&V offsets are from top of picture.
|
||||
|
||||
friend class PVideoFrame;
|
||||
void AddRef() { InterlockedIncrement((long *)&refcount); }
|
||||
void Release() { if (refcount==1) InterlockedDecrement(&vfb->refcount); InterlockedDecrement((long *)&refcount); }
|
||||
|
||||
friend class ScriptEnvironment;
|
||||
friend class Cache;
|
||||
|
||||
VideoFrame(VideoFrameBuffer* _vfb, int _offset, int _pitch, int _row_size, int _height);
|
||||
VideoFrame(VideoFrameBuffer* _vfb, int _offset, int _pitch, int _row_size, int _height, int _offsetU, int _offsetV, int _pitchUV);
|
||||
|
||||
void* operator new(size_t size);
|
||||
// TESTME: OFFSET U/V may be switched to what could be expected from AVI standard!
|
||||
public:
|
||||
int GetPitch() const { return pitch; }
|
||||
int GetPitch(int plane) const { switch (plane) {case PLANAR_U: case PLANAR_V: return pitchUV;} return pitch; }
|
||||
int GetRowSize() const { return row_size; }
|
||||
int GetRowSize(int plane) const {
|
||||
switch (plane) {
|
||||
case PLANAR_U: case PLANAR_V: if (pitchUV) return row_size>>1; else return 0;
|
||||
case PLANAR_U_ALIGNED: case PLANAR_V_ALIGNED:
|
||||
if (pitchUV) {
|
||||
int r = ((row_size+FRAME_ALIGN-1)&(~(FRAME_ALIGN-1)) )>>1; // Aligned rowsize
|
||||
if (r<=pitchUV)
|
||||
return r;
|
||||
return row_size>>1;
|
||||
} else return 0;
|
||||
case PLANAR_Y_ALIGNED:
|
||||
int r = (row_size+FRAME_ALIGN-1)&(~(FRAME_ALIGN-1)); // Aligned rowsize
|
||||
if (r<=pitch)
|
||||
return r;
|
||||
return row_size;
|
||||
}
|
||||
return row_size; }
|
||||
int GetHeight() const { return height; }
|
||||
int GetHeight(int plane) const { switch (plane) {case PLANAR_U: case PLANAR_V: if (pitchUV) return height>>1; return 0;} return height; }
|
||||
|
||||
// generally you shouldn't use these three
|
||||
VideoFrameBuffer* GetFrameBuffer() const { return vfb; }
|
||||
int GetOffset() const { return offset; }
|
||||
int GetOffset(int plane) const { switch (plane) {case PLANAR_U: return offsetU;case PLANAR_V: return offsetV;default: return offset;}; }
|
||||
|
||||
// in plugins use env->SubFrame()
|
||||
VideoFrame* Subframe(int rel_offset, int new_pitch, int new_row_size, int new_height) const;
|
||||
VideoFrame* Subframe(int rel_offset, int new_pitch, int new_row_size, int new_height, int rel_offsetU, int rel_offsetV, int pitchUV) const;
|
||||
|
||||
|
||||
const BYTE* GetReadPtr() const { return vfb->GetReadPtr() + offset; }
|
||||
const BYTE* GetReadPtr(int plane) const { return vfb->GetReadPtr() + GetOffset(plane); }
|
||||
|
||||
bool IsWritable() const { return (refcount == 1 && vfb->refcount == 1); }
|
||||
|
||||
BYTE* GetWritePtr() const {
|
||||
if (vfb->GetRefcount()>1) {
|
||||
_ASSERT(FALSE);
|
||||
//throw AvisynthError("Internal Error - refcount was more than one!");
|
||||
}
|
||||
return IsWritable() ? (vfb->GetWritePtr() + offset) : 0;
|
||||
}
|
||||
|
||||
BYTE* GetWritePtr(int plane) const {
|
||||
if (plane==PLANAR_Y) {
|
||||
if (vfb->GetRefcount()>1) {
|
||||
_ASSERT(FALSE);
|
||||
// throw AvisynthError("Internal Error - refcount was more than one!");
|
||||
}
|
||||
return IsWritable() ? vfb->GetWritePtr() + GetOffset(plane) : 0;
|
||||
}
|
||||
return vfb->data + GetOffset(plane);
|
||||
}
|
||||
|
||||
~VideoFrame() { InterlockedDecrement(&vfb->refcount); }
|
||||
};
|
||||
|
||||
enum {
|
||||
CACHE_NOTHING=0,
|
||||
CACHE_RANGE=1,
|
||||
CACHE_ALL=2,
|
||||
CACHE_AUDIO=3,
|
||||
CACHE_AUDIO_NONE=4
|
||||
};
|
||||
|
||||
// Base class for all filters.
|
||||
class IClip {
|
||||
friend class PClip;
|
||||
friend class AVSValue;
|
||||
int refcnt;
|
||||
void AddRef() { InterlockedIncrement((long *)&refcnt); }
|
||||
void Release() { InterlockedDecrement((long *)&refcnt); if (!refcnt) delete this; }
|
||||
public:
|
||||
IClip() : refcnt(0) {}
|
||||
|
||||
virtual int __stdcall GetVersion() { return AVISYNTH_INTERFACE_VERSION; }
|
||||
|
||||
virtual PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env) = 0;
|
||||
virtual bool __stdcall GetParity(int n) = 0; // return field parity if field_based, else parity of first field in frame
|
||||
virtual void __stdcall GetAudio(void* buf, __int64 start, __int64 count, IScriptEnvironment* env) = 0; // start and count are in samples
|
||||
virtual void __stdcall SetCacheHints(int cachehints,int frame_range) = 0 ; // We do not pass cache requests upwards, only to the next filter.
|
||||
virtual const VideoInfo& __stdcall GetVideoInfo() = 0;
|
||||
virtual __stdcall ~IClip() {}
|
||||
};
|
||||
|
||||
|
||||
// smart pointer to IClip
|
||||
class PClip {
|
||||
|
||||
IClip* p;
|
||||
|
||||
IClip* GetPointerWithAddRef() const { if (p) p->AddRef(); return p; }
|
||||
friend class AVSValue;
|
||||
friend class VideoFrame;
|
||||
|
||||
void Init(IClip* x) {
|
||||
if (x) x->AddRef();
|
||||
p=x;
|
||||
}
|
||||
void Set(IClip* x) {
|
||||
if (x) x->AddRef();
|
||||
if (p) p->Release();
|
||||
p=x;
|
||||
}
|
||||
|
||||
public:
|
||||
PClip() { p = 0; }
|
||||
PClip(const PClip& x) { Init(x.p); }
|
||||
PClip(IClip* x) { Init(x); }
|
||||
void operator=(IClip* x) { Set(x); }
|
||||
void operator=(const PClip& x) { Set(x.p); }
|
||||
|
||||
IClip* operator->() const { return p; }
|
||||
|
||||
// useful in conditional expressions
|
||||
operator void*() const { return p; }
|
||||
bool operator!() const { return !p; }
|
||||
|
||||
~PClip() { if (p) p->Release(); }
|
||||
};
|
||||
|
||||
|
||||
// smart pointer to VideoFrame
|
||||
class PVideoFrame {
|
||||
|
||||
VideoFrame* p;
|
||||
|
||||
void Init(VideoFrame* x) {
|
||||
if (x) x->AddRef();
|
||||
p=x;
|
||||
}
|
||||
void Set(VideoFrame* x) {
|
||||
if (x) x->AddRef();
|
||||
if (p) p->Release();
|
||||
p=x;
|
||||
}
|
||||
|
||||
public:
|
||||
PVideoFrame() { p = 0; }
|
||||
PVideoFrame(const PVideoFrame& x) { Init(x.p); }
|
||||
PVideoFrame(VideoFrame* x) { Init(x); }
|
||||
void operator=(VideoFrame* x) { Set(x); }
|
||||
void operator=(const PVideoFrame& x) { Set(x.p); }
|
||||
|
||||
VideoFrame* operator->() const { return p; }
|
||||
|
||||
// for conditional expressions
|
||||
operator void*() const { return p; }
|
||||
bool operator!() const { return !p; }
|
||||
|
||||
~PVideoFrame() { if (p) p->Release();}
|
||||
};
|
||||
|
||||
|
||||
class AVSValue {
|
||||
public:
|
||||
|
||||
AVSValue() { type = 'v'; }
|
||||
AVSValue(IClip* c) { type = 'c'; clip = c; if (c) c->AddRef(); }
|
||||
AVSValue(const PClip& c) { type = 'c'; clip = c.GetPointerWithAddRef(); }
|
||||
AVSValue(bool b) { type = 'b'; boolean = b; }
|
||||
AVSValue(int i) { type = 'i'; integer = i; }
|
||||
// AVSValue(__int64 l) { type = 'l'; longlong = l; }
|
||||
AVSValue(float f) { type = 'f'; floating_pt = f; }
|
||||
AVSValue(double f) { type = 'f'; floating_pt = float(f); }
|
||||
AVSValue(const char* s) { type = 's'; string = s; }
|
||||
AVSValue(const AVSValue* a, int size) { type = 'a'; array = a; array_size = size; }
|
||||
AVSValue(const AVSValue& v) { Assign(&v, true); }
|
||||
|
||||
~AVSValue() { if (IsClip() && clip) clip->Release(); }
|
||||
AVSValue& operator=(const AVSValue& v) { Assign(&v, false); return *this; }
|
||||
|
||||
// Note that we transparently allow 'int' to be treated as 'float'.
|
||||
// There are no int<->bool conversions, though.
|
||||
|
||||
bool Defined() const { return type != 'v'; }
|
||||
bool IsClip() const { return type == 'c'; }
|
||||
bool IsBool() const { return type == 'b'; }
|
||||
bool IsInt() const { return type == 'i'; }
|
||||
// bool IsLong() const { return (type == 'l'|| type == 'i'); }
|
||||
bool IsFloat() const { return type == 'f' || type == 'i'; }
|
||||
bool IsString() const { return type == 's'; }
|
||||
bool IsArray() const { return type == 'a'; }
|
||||
|
||||
PClip AsClip() const { _ASSERTE(IsClip()); return IsClip()?clip:0; }
|
||||
bool AsBool() const { _ASSERTE(IsBool()); return boolean; }
|
||||
int AsInt() const { _ASSERTE(IsInt()); return integer; }
|
||||
// int AsLong() const { _ASSERTE(IsLong()); return longlong; }
|
||||
const char* AsString() const { _ASSERTE(IsString()); return IsString()?string:0; }
|
||||
double AsFloat() const { _ASSERTE(IsFloat()); return IsInt()?integer:floating_pt; }
|
||||
|
||||
bool AsBool(bool def) const { _ASSERTE(IsBool()||!Defined()); return IsBool() ? boolean : def; }
|
||||
int AsInt(int def) const { _ASSERTE(IsInt()||!Defined()); return IsInt() ? integer : def; }
|
||||
double AsFloat(double def) const { _ASSERTE(IsFloat()||!Defined()); return IsInt() ? integer : type=='f' ? floating_pt : def; }
|
||||
const char* AsString(const char* def) const { _ASSERTE(IsString()||!Defined()); return IsString() ? string : def; }
|
||||
|
||||
int ArraySize() const { _ASSERTE(IsArray()); return IsArray()?array_size:1; }
|
||||
|
||||
const AVSValue& operator[](int index) const {
|
||||
_ASSERTE(IsArray() && index>=0 && index<array_size);
|
||||
return (IsArray() && index>=0 && index<array_size) ? array[index] : *this;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
short type; // 'a'rray, 'c'lip, 'b'ool, 'i'nt, 'f'loat, 's'tring, 'v'oid, or 'l'ong
|
||||
short array_size;
|
||||
union {
|
||||
IClip* clip;
|
||||
bool boolean;
|
||||
int integer;
|
||||
float floating_pt;
|
||||
const char* string;
|
||||
const AVSValue* array;
|
||||
// __int64 longlong;
|
||||
};
|
||||
|
||||
void Assign(const AVSValue* src, bool init) {
|
||||
if (src->IsClip() && src->clip)
|
||||
src->clip->AddRef();
|
||||
if (!init && IsClip() && clip)
|
||||
clip->Release();
|
||||
// make sure this copies the whole struct!
|
||||
//((__int32*)this)[0] = ((__int32*)src)[0];
|
||||
//((__int32*)this)[1] = ((__int32*)src)[1];
|
||||
memcpy(this, src, sizeof(AVSValue));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// instantiable null filter
|
||||
class GenericVideoFilter : public IClip {
|
||||
protected:
|
||||
PClip child;
|
||||
VideoInfo vi;
|
||||
public:
|
||||
GenericVideoFilter(PClip _child) : child(_child) { vi = child->GetVideoInfo(); }
|
||||
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env) { return child->GetFrame(n, env); }
|
||||
void __stdcall GetAudio(void* buf, __int64 start, __int64 count, IScriptEnvironment* env) { child->GetAudio(buf, start, count, env); }
|
||||
const VideoInfo& __stdcall GetVideoInfo() { return vi; }
|
||||
bool __stdcall GetParity(int n) { return child->GetParity(n); }
|
||||
void __stdcall SetCacheHints(int cachehints,int frame_range) { } ; // We do not pass cache requests upwards, only to the next filter.
|
||||
};
|
||||
|
||||
|
||||
class AvisynthError /* exception */ {
|
||||
public:
|
||||
const char* const msg;
|
||||
AvisynthError(const char* _msg) : msg(_msg) {}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/* Helper classes useful to plugin authors */
|
||||
|
||||
class AlignPlanar : public GenericVideoFilter
|
||||
{
|
||||
public:
|
||||
AlignPlanar(PClip _clip);
|
||||
static PClip Create(PClip clip);
|
||||
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env);
|
||||
};
|
||||
|
||||
|
||||
|
||||
class FillBorder : public GenericVideoFilter
|
||||
{
|
||||
public:
|
||||
FillBorder(PClip _clip);
|
||||
static PClip Create(PClip clip);
|
||||
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env);
|
||||
};
|
||||
|
||||
|
||||
|
||||
class ConvertAudio : public GenericVideoFilter
|
||||
/**
|
||||
* Helper class to convert audio to any format
|
||||
**/
|
||||
{
|
||||
public:
|
||||
ConvertAudio(PClip _clip, int prefered_format);
|
||||
void __stdcall GetAudio(void* buf, __int64 start, __int64 count, IScriptEnvironment* env);
|
||||
void __stdcall SetCacheHints(int cachehints,int frame_range); // We do pass cache requests upwards, to the cache!
|
||||
|
||||
static PClip Create(PClip clip, int sample_type, int prefered_type);
|
||||
static AVSValue __cdecl Create_float(AVSValue args, void*, IScriptEnvironment*);
|
||||
static AVSValue __cdecl Create_32bit(AVSValue args, void*, IScriptEnvironment*);
|
||||
static AVSValue __cdecl Create_24bit(AVSValue args, void*, IScriptEnvironment*);
|
||||
static AVSValue __cdecl Create_16bit(AVSValue args, void*, IScriptEnvironment*);
|
||||
static AVSValue __cdecl Create_8bit(AVSValue args, void*, IScriptEnvironment*);
|
||||
virtual ~ConvertAudio();
|
||||
|
||||
private:
|
||||
void convertToFloat(char* inbuf, float* outbuf, char sample_type, int count);
|
||||
void convertToFloat_3DN(char* inbuf, float* outbuf, char sample_type, int count);
|
||||
void convertToFloat_SSE(char* inbuf, float* outbuf, char sample_type, int count);
|
||||
void convertToFloat_SSE2(char* inbuf, float* outbuf, char sample_type, int count);
|
||||
void convertFromFloat(float* inbuf, void* outbuf, char sample_type, int count);
|
||||
void convertFromFloat_3DN(float* inbuf, void* outbuf, char sample_type, int count);
|
||||
void convertFromFloat_SSE(float* inbuf, void* outbuf, char sample_type, int count);
|
||||
void convertFromFloat_SSE2(float* inbuf, void* outbuf, char sample_type, int count);
|
||||
|
||||
__inline int Saturate_int8(float n);
|
||||
__inline short Saturate_int16(float n);
|
||||
__inline int Saturate_int24(float n);
|
||||
__inline int Saturate_int32(float n);
|
||||
|
||||
char src_format;
|
||||
char dst_format;
|
||||
int src_bps;
|
||||
char *tempbuffer;
|
||||
SFLOAT *floatbuffer;
|
||||
int tempbuffer_size;
|
||||
};
|
||||
|
||||
|
||||
// For GetCPUFlags. These are backwards-compatible with those in VirtualDub.
|
||||
enum {
|
||||
/* slowest CPU to support extension */
|
||||
CPUF_FORCE = 0x01, // N/A
|
||||
CPUF_FPU = 0x02, // 386/486DX
|
||||
CPUF_MMX = 0x04, // P55C, K6, PII
|
||||
CPUF_INTEGER_SSE = 0x08, // PIII, Athlon
|
||||
CPUF_SSE = 0x10, // PIII, Athlon XP/MP
|
||||
CPUF_SSE2 = 0x20, // PIV, Hammer
|
||||
CPUF_3DNOW = 0x40, // K6-2
|
||||
CPUF_3DNOW_EXT = 0x80, // Athlon
|
||||
CPUF_X86_64 = 0xA0, // Hammer (note: equiv. to 3DNow + SSE2, which only Hammer
|
||||
// will have anyway)
|
||||
CPUF_SSE3 = 0x100, // Some P4 & Athlon 64.
|
||||
};
|
||||
#define MAX_INT 0x7fffffff
|
||||
#define MIN_INT -0x7fffffff
|
||||
|
||||
|
||||
|
||||
class IScriptEnvironment {
|
||||
public:
|
||||
virtual __stdcall ~IScriptEnvironment() {}
|
||||
|
||||
virtual /*static*/ long __stdcall GetCPUFlags() = 0;
|
||||
|
||||
virtual char* __stdcall SaveString(const char* s, int length = -1) = 0;
|
||||
virtual char* __stdcall Sprintf(const char* fmt, ...) = 0;
|
||||
// note: val is really a va_list; I hope everyone typedefs va_list to a pointer
|
||||
virtual char* __stdcall VSprintf(const char* fmt, void* val) = 0;
|
||||
|
||||
__declspec(noreturn) virtual void __stdcall ThrowError(const char* fmt, ...) = 0;
|
||||
|
||||
class NotFound /*exception*/ {}; // thrown by Invoke and GetVar
|
||||
|
||||
typedef AVSValue (__cdecl *ApplyFunc)(AVSValue args, void* user_data, IScriptEnvironment* env);
|
||||
|
||||
virtual void __stdcall AddFunction(const char* name, const char* params, ApplyFunc apply, void* user_data) = 0;
|
||||
virtual bool __stdcall FunctionExists(const char* name) = 0;
|
||||
virtual AVSValue __stdcall Invoke(const char* name, const AVSValue args, const char** arg_names=0) = 0;
|
||||
|
||||
virtual AVSValue __stdcall GetVar(const char* name) = 0;
|
||||
virtual bool __stdcall SetVar(const char* name, const AVSValue& val) = 0;
|
||||
virtual bool __stdcall SetGlobalVar(const char* name, const AVSValue& val) = 0;
|
||||
|
||||
virtual void __stdcall PushContext(int level=0) = 0;
|
||||
virtual void __stdcall PopContext() = 0;
|
||||
|
||||
// align should be 4 or 8
|
||||
virtual PVideoFrame __stdcall NewVideoFrame(const VideoInfo& vi, int align=FRAME_ALIGN) = 0;
|
||||
|
||||
virtual bool __stdcall MakeWritable(PVideoFrame* pvf) = 0;
|
||||
|
||||
virtual /*static*/ void __stdcall BitBlt(BYTE* dstp, int dst_pitch, const BYTE* srcp, int src_pitch, int row_size, int height) = 0;
|
||||
|
||||
typedef void (__cdecl *ShutdownFunc)(void* user_data, IScriptEnvironment* env);
|
||||
virtual void __stdcall AtExit(ShutdownFunc function, void* user_data) = 0;
|
||||
|
||||
virtual void __stdcall CheckVersion(int version = AVISYNTH_INTERFACE_VERSION) = 0;
|
||||
|
||||
virtual PVideoFrame __stdcall Subframe(PVideoFrame src, int rel_offset, int new_pitch, int new_row_size, int new_height) = 0;
|
||||
|
||||
virtual int __stdcall SetMemoryMax(int mem) = 0;
|
||||
|
||||
virtual int __stdcall SetWorkingDir(const char * newdir) = 0;
|
||||
|
||||
virtual void* __stdcall ManageCache(int key, void* data) = 0;
|
||||
|
||||
enum PlanarChromaAlignmentMode {
|
||||
PlanarChromaAlignmentOff,
|
||||
PlanarChromaAlignmentOn,
|
||||
PlanarChromaAlignmentTest };
|
||||
|
||||
virtual bool __stdcall PlanarChromaAlignment(PlanarChromaAlignmentMode key) = 0;
|
||||
|
||||
virtual PVideoFrame __stdcall SubframePlanar(PVideoFrame src, int rel_offset, int new_pitch, int new_row_size, int new_height, int rel_offsetU, int rel_offsetV, int new_pitchUV) = 0;
|
||||
};
|
||||
|
||||
|
||||
// avisynth.dll exports this; it's a way to use it as a library, without
|
||||
// writing an AVS script or without going through AVIFile.
|
||||
IScriptEnvironment* __stdcall CreateScriptEnvironment(int version = AVISYNTH_INTERFACE_VERSION);
|
||||
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif //__AVISYNTH_H__
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
#ifdef WITH_AVISYNTH
|
||||
#include "avisynth_wrap.h"
|
||||
|
||||
#include "avisynth.h"
|
||||
#include <avisynth.h>
|
||||
#include "options.h"
|
||||
|
||||
#include <mutex>
|
||||
|
|
BIN
src/bitmaps/button/button_align_16.png
Normal file
BIN
src/bitmaps/button/button_align_16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 353 B |
BIN
src/bitmaps/button/button_align_24.png
Normal file
BIN
src/bitmaps/button/button_align_24.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 571 B |
BIN
src/bitmaps/button/button_align_32.png
Normal file
BIN
src/bitmaps/button/button_align_32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 690 B |
BIN
src/bitmaps/button/button_align_48.png
Normal file
BIN
src/bitmaps/button/button_align_48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
src/bitmaps/button/button_align_64.png
Normal file
BIN
src/bitmaps/button/button_align_64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 547 B |
|
@ -51,6 +51,11 @@ button/bugtracker_button_24.png
|
|||
button/bugtracker_button_32.png
|
||||
button/bugtracker_button_48.png
|
||||
button/bugtracker_button_64.png
|
||||
button/button_align_16.png
|
||||
button/button_align_24.png
|
||||
button/button_align_32.png
|
||||
button/button_align_48.png
|
||||
button/button_align_64.png
|
||||
button/button_audio_commit_16.png
|
||||
button/button_audio_commit_24.png
|
||||
button/button_audio_commit_32.png
|
||||
|
|
|
@ -150,8 +150,11 @@ public:
|
|||
}
|
||||
|
||||
// Remove old entries until we're under the max size
|
||||
for (auto it = age.rbegin(); size > max_size && it != age.rend(); )
|
||||
KillMacroBlock(**it++);
|
||||
while (size > max_size) {
|
||||
// When size > 0, age should never be empty
|
||||
assert(!age.empty());
|
||||
KillMacroBlock(*age.back());
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Obtain a data block from the cache
|
||||
|
|
|
@ -18,9 +18,13 @@
|
|||
|
||||
#include "dialogs.h"
|
||||
|
||||
#if BOOST_VERSION >= 106900
|
||||
#include <boost/gil.hpp>
|
||||
#else
|
||||
#include <boost/gil/gil_all.hpp>
|
||||
#endif
|
||||
|
||||
AGI_DEFINE_EVENT(EVT_COLOR, agi::Color);
|
||||
AGI_DEFINE_EVENT(EVT_COLOR, agi::Color)
|
||||
|
||||
ColourButton::ColourButton(wxWindow *parent, wxSize const& size, bool alpha, agi::Color col, wxValidator const& validator)
|
||||
: wxButton(parent, -1, "", wxDefaultPosition, wxSize(size.GetWidth() + 6, size.GetHeight() + 6), 0, validator)
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
/// Emitted by ColourButton when the user picks a new color, with the chosen
|
||||
/// color set to the event payload
|
||||
AGI_DECLARE_EVENT(EVT_COLOR, agi::Color);
|
||||
AGI_DECLARE_EVENT(EVT_COLOR, agi::Color)
|
||||
|
||||
/// A button which displays a currently-selected color and lets the user pick
|
||||
/// a new color when clicked
|
||||
|
@ -43,6 +43,12 @@ public:
|
|||
|
||||
/// Get the currently selected color
|
||||
agi::Color GetColor() { return colour; }
|
||||
|
||||
void SetColor(agi::Color color)
|
||||
{
|
||||
colour = color;
|
||||
UpdateBitmap();
|
||||
}
|
||||
};
|
||||
|
||||
struct ColorValidator final : public wxValidator {
|
||||
|
|
|
@ -80,7 +80,7 @@ struct audio_open final : public Command {
|
|||
STR_HELP("Open an audio file")
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
auto str = from_wx(_("Audio Formats") + " (*.aac,*.ac3,*.ape,*.dts,*.flac,*.m4a,*.mka,*.mp3,*.mp4,*.ogg,*.w64,*.wav,*.wma)|*.aac;*.ac3;*.ape;*.dts;*.flac;*.m4a;*.mka;*.mp3;*.mp4;*.ogg;*.w64;*.wav;*.wma|"
|
||||
auto str = from_wx(_("Audio Formats") + " (*.aac,*.ac3,*.ape,*.dts,*.eac3,*.flac,*.m4a,*.mka,*.mp3,*.mp4,*.ogg,*.opus,*.w64,*.wav,*.wma)|*.aac;*.ac3;*.ape;*.dts;*.eac3;*.flac;*.m4a;*.mka;*.mp3;*.mp4;*.ogg;*.opus;*.w64;*.wav;*.wma|"
|
||||
+ _("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts)|*.asf;*.avi;*.avs;*.d2v;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts|"
|
||||
+ _("All Files") + " (*.*)|*.*");
|
||||
auto filename = OpenFileSelector(_("Open Audio File"), "Path/Last/Audio", "", "", str, c->parent);
|
||||
|
|
|
@ -35,10 +35,10 @@ namespace agi { struct Context; }
|
|||
#define CMD_TYPE(a) int Type() const override { using namespace cmd; return a; }
|
||||
|
||||
#define CMD_ICON(icon) wxBitmap Icon(int size, wxLayoutDirection dir = wxLayout_LeftToRight) const override { \
|
||||
if (size == 64) return GETIMAGEDIR(icon##_64, dir); \
|
||||
if (size == 48) return GETIMAGEDIR(icon##_48, dir); \
|
||||
if (size == 32) return GETIMAGEDIR(icon##_32, dir); \
|
||||
if (size == 24) return GETIMAGEDIR(icon##_24, dir); \
|
||||
if (size >= 64) return GETIMAGEDIR(icon##_64, dir); \
|
||||
if (size >= 48) return GETIMAGEDIR(icon##_48, dir); \
|
||||
if (size >= 32) return GETIMAGEDIR(icon##_32, dir); \
|
||||
if (size >= 24) return GETIMAGEDIR(icon##_24, dir); \
|
||||
return GETIMAGEDIR(icon##_16, dir); \
|
||||
}
|
||||
|
||||
|
|
|
@ -697,8 +697,8 @@ static void duplicate_lines(agi::Context *c, int shift) {
|
|||
new_diag->Start = c->videoController->TimeAtFrame(cur_frame, agi::vfr::START);
|
||||
}
|
||||
else {
|
||||
old_diag->Start = c->videoController->TimeAtFrame(cur_frame + 1, agi::vfr::START);
|
||||
new_diag->End = c->videoController->TimeAtFrame(cur_frame, agi::vfr::END);
|
||||
old_diag->End = c->videoController->TimeAtFrame(cur_frame, agi::vfr::END);
|
||||
new_diag->Start = c->videoController->TimeAtFrame(cur_frame + 1, agi::vfr::START);
|
||||
}
|
||||
|
||||
/// @todo also split \t and \move?
|
||||
|
|
|
@ -59,7 +59,7 @@ struct help_bugs final : public Command {
|
|||
throw c->parent;
|
||||
}
|
||||
}
|
||||
wxLaunchDefaultBrowser("http://devel.aegisub.org/", wxBROWSER_NEW_WINDOW);
|
||||
wxLaunchDefaultBrowser("https://github.com/Aegisub/Aegisub/issues", wxBROWSER_NEW_WINDOW);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -75,18 +75,6 @@ struct help_contents final : public Command {
|
|||
}
|
||||
};
|
||||
|
||||
struct help_forums final : public Command {
|
||||
CMD_NAME("help/forums")
|
||||
CMD_ICON(forums_button)
|
||||
STR_MENU("&Forums")
|
||||
STR_DISP("Forums")
|
||||
STR_HELP("Visit Aegisub's forums")
|
||||
|
||||
void operator()(agi::Context *) override {
|
||||
wxLaunchDefaultBrowser("http://forum.aegisub.org/", wxBROWSER_NEW_WINDOW);
|
||||
}
|
||||
};
|
||||
|
||||
struct help_irc final : public Command {
|
||||
CMD_NAME("help/irc")
|
||||
CMD_ICON(irc_button)
|
||||
|
@ -128,7 +116,6 @@ namespace cmd {
|
|||
void init_help() {
|
||||
reg(agi::make_unique<help_bugs>());
|
||||
reg(agi::make_unique<help_contents>());
|
||||
reg(agi::make_unique<help_forums>());
|
||||
reg(agi::make_unique<help_irc>());
|
||||
reg(agi::make_unique<help_video>());
|
||||
reg(agi::make_unique<help_website>());
|
||||
|
|
|
@ -235,6 +235,18 @@ struct time_snap_scene final : public validate_video_loaded {
|
|||
}
|
||||
};
|
||||
|
||||
struct time_align_subtitle_to_point final : public validate_video_loaded {
|
||||
CMD_NAME("time/align")
|
||||
CMD_ICON(button_align)
|
||||
STR_MENU("Align subtitle to video")
|
||||
STR_DISP("Align subtitle to video")
|
||||
STR_HELP("Align subtitle to video by key points")
|
||||
void operator()(agi::Context* c) override {
|
||||
c->videoController->Stop();
|
||||
ShowAlignToVideoDialog(c);
|
||||
}
|
||||
};
|
||||
|
||||
struct time_add_lead_both final : public Command {
|
||||
CMD_NAME("time/lead/both")
|
||||
STR_MENU("Add lead in and out")
|
||||
|
@ -393,6 +405,7 @@ namespace cmd {
|
|||
reg(agi::make_unique<time_snap_end_video>());
|
||||
reg(agi::make_unique<time_snap_scene>());
|
||||
reg(agi::make_unique<time_snap_start_video>());
|
||||
reg(agi::make_unique<time_align_subtitle_to_point>());
|
||||
reg(agi::make_unique<time_start_decrease>());
|
||||
reg(agi::make_unique<time_start_increase>());
|
||||
}
|
||||
|
|
|
@ -564,7 +564,7 @@ struct video_open final : public Command {
|
|||
STR_HELP("Open a video file")
|
||||
|
||||
void operator()(agi::Context *c) override {
|
||||
auto str = from_wx(_("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts,*.y4m,*.yuv)|*.asf;*.avi;*.avs;*.d2v;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts;*.y4m;*.yuv|"
|
||||
auto str = from_wx(_("Video Formats") + " (*.asf,*.avi,*.avs,*.d2v,*.h264,*.hevc,*.m2ts,*.m4v,*.mkv,*.mov,*.mp4,*.mpeg,*.mpg,*.ogm,*.webm,*.wmv,*.ts,*.y4m,*.yuv)|*.asf;*.avi;*.avs;*.d2v;*.h264;*.hevc;*.m2ts;*.m4v;*.mkv;*.mov;*.mp4;*.mpeg;*.mpg;*.ogm;*.webm;*.wmv;*.ts;*.y4m;*.yuv|"
|
||||
+ _("All Files") + " (*.*)|*.*");
|
||||
auto filename = OpenFileSelector(_("Open video file"), "Path/Last/Video", "", "", str, c->parent);
|
||||
if (!filename.empty())
|
||||
|
|
|
@ -49,10 +49,11 @@ void ShowAboutDialog(wxWindow *parent) {
|
|||
|
||||
// Generate about string
|
||||
wxString aboutString = wxString("Aegisub ") + GetAegisubShortVersionString() + ".\n"
|
||||
"Copyright (c) 2005-2014 Rodrigo Braz Monteiro, Niels Martin Hansen, Thomas Goyne et al.\n\n"
|
||||
"Copyright (c) 2005-2019 Rodrigo Braz Monteiro, Niels Martin Hansen, Thomas Goyne et al.\n\n"
|
||||
"Programmers:\n"
|
||||
" Alysson Souza e Silva\n"
|
||||
" Amar Takhar\n"
|
||||
" Charlie Jiang\n"
|
||||
" Dan Donovan\n"
|
||||
" Daniel Moscoviter\n"
|
||||
" David Conrad\n"
|
||||
|
@ -67,6 +68,7 @@ void ShowAboutDialog(wxWindow *parent) {
|
|||
" Muhammad Lukman Nasaruddin\n"
|
||||
" Niels Martin Hansen\n"
|
||||
" Patryk Pomykalski\n"
|
||||
" Qirui Wang\n"
|
||||
" Ravi Pinjala\n"
|
||||
" Rodrigo Braz Monteiro\n"
|
||||
" Simone Cociancich\n"
|
||||
|
@ -129,7 +131,12 @@ void ShowAboutDialog(wxWindow *parent) {
|
|||
wxChar copySymbol = 0xA9;
|
||||
aboutString.Replace("(c)", wxString(copySymbol));
|
||||
|
||||
wxTextCtrl *textctrl = new wxTextCtrl(&d, -1, aboutString, wxDefaultPosition, wxSize(-1, 200), wxTE_MULTILINE | wxTE_READONLY | wxBORDER_NONE);
|
||||
wxTextCtrl *textctrl = new wxTextCtrl(&d, -1, aboutString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY | wxBORDER_NONE);
|
||||
#if defined(__WXGTK__) && !wxCHECK_VERSION(3, 1, 3)
|
||||
// Workaround for https://trac.wxwidgets.org/ticket/18507
|
||||
textctrl->InvalidateBestSize();
|
||||
textctrl->SetInitialSize();
|
||||
#endif
|
||||
|
||||
wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL);
|
||||
MainSizer->Add(new wxStaticBitmap(&d, -1, GETIMAGE(splash)), 0, wxCENTER, 0);
|
||||
|
|
376
src/dialog_align.cpp
Normal file
376
src/dialog_align.cpp
Normal file
|
@ -0,0 +1,376 @@
|
|||
// Copyright (c) 2019, Charlie Jiang
|
||||
// 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 the Aegisub Group 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 OWNER 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.
|
||||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
|
||||
#include "ass_dialogue.h"
|
||||
#include "ass_file.h"
|
||||
#include "compat.h"
|
||||
#include "dialog_manager.h"
|
||||
#include "format.h"
|
||||
#include "include/aegisub/context.h"
|
||||
#include "video_frame.h"
|
||||
#include "libresrc/libresrc.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "selection_controller.h"
|
||||
#include "video_controller.h"
|
||||
#include "async_video_provider.h"
|
||||
#include "colour_button.h"
|
||||
#include "image_position_picker.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <libaegisub/ass/time.h>
|
||||
#include <libaegisub/vfr.h>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/textctrl.h>
|
||||
#if BOOST_VERSION >= 106900
|
||||
#include <boost/gil.hpp>
|
||||
#else
|
||||
#include <boost/gil/gil_all.hpp>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
class DialogAlignToVideo final : public wxDialog {
|
||||
agi::Context* context;
|
||||
AsyncVideoProvider* provider;
|
||||
|
||||
wxImage preview_image;
|
||||
VideoFrame current_frame;
|
||||
int current_n_frame;
|
||||
|
||||
ImagePositionPicker* preview_frame;
|
||||
ColourButton* selected_color;
|
||||
wxTextCtrl* selected_x;
|
||||
wxTextCtrl* selected_y;
|
||||
wxTextCtrl* selected_tolerance;
|
||||
|
||||
void update_from_textbox();
|
||||
void update_from_textbox(wxCommandEvent&);
|
||||
|
||||
bool check_exists(int pos, int x, int y, int* lrud, double* orig, unsigned char tolerance);
|
||||
void process(wxEvent&);
|
||||
public:
|
||||
DialogAlignToVideo(agi::Context* context);
|
||||
~DialogAlignToVideo();
|
||||
};
|
||||
|
||||
DialogAlignToVideo::DialogAlignToVideo(agi::Context* context)
|
||||
: wxDialog(context->parent, -1, _("Align subtitle to video by key point"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxMAXIMIZE_BOX | wxRESIZE_BORDER)
|
||||
, context(context), provider(context->project->VideoProvider())
|
||||
{
|
||||
auto add_with_label = [&](wxSizer * sizer, wxString const& label, wxWindow * ctrl) {
|
||||
sizer->Add(new wxStaticText(this, -1, label), 0, wxLEFT | wxRIGHT | wxCENTER, 3);
|
||||
sizer->Add(ctrl, 1, wxLEFT);
|
||||
};
|
||||
|
||||
auto tolerance = OPT_GET("Tool/Align to Video/Tolerance")->GetInt();
|
||||
auto maximized = OPT_GET("Tool/Align to Video/Maximized")->GetBool();
|
||||
|
||||
current_n_frame = context->videoController->GetFrameN();
|
||||
current_frame = *context->project->VideoProvider()->GetFrame(current_n_frame, 0, true);
|
||||
preview_image = GetImage(current_frame);
|
||||
|
||||
preview_frame = new ImagePositionPicker(this, preview_image, [&](int x, int y, unsigned char r, unsigned char g, unsigned char b) -> void {
|
||||
selected_x->ChangeValue(wxString::Format(wxT("%i"), x));
|
||||
selected_y->ChangeValue(wxString::Format(wxT("%i"), y));
|
||||
|
||||
selected_color->SetColor(agi::Color(r, g, b));
|
||||
});
|
||||
selected_color = new ColourButton(this, wxSize(55, 16), true, agi::Color("FFFFFF"));
|
||||
selected_color->SetToolTip(_("The key color to be followed."));
|
||||
selected_x = new wxTextCtrl(this, -1, "0");
|
||||
selected_x->SetToolTip(_("The x coord of the key point."));
|
||||
selected_y = new wxTextCtrl(this, -1, "0");
|
||||
selected_y->SetToolTip(_("The y coord of the key point."));
|
||||
selected_tolerance = new wxTextCtrl(this, -1, wxString::Format(wxT("%i"), int(tolerance)));
|
||||
selected_tolerance->SetToolTip(_("Max tolerance of the color."));
|
||||
|
||||
selected_x->Bind(wxEVT_TEXT, &DialogAlignToVideo::update_from_textbox, this);
|
||||
selected_y->Bind(wxEVT_TEXT, &DialogAlignToVideo::update_from_textbox, this);
|
||||
update_from_textbox();
|
||||
|
||||
wxFlexGridSizer* right_sizer = new wxFlexGridSizer(4, 2, 5, 5);
|
||||
add_with_label(right_sizer, _("X"), selected_x);
|
||||
add_with_label(right_sizer, _("Y"), selected_y);
|
||||
add_with_label(right_sizer, _("Color"), selected_color);
|
||||
add_with_label(right_sizer, _("Tolerance"), selected_tolerance);
|
||||
right_sizer->AddGrowableCol(1, 1);
|
||||
|
||||
wxSizer* main_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
main_sizer->Add(preview_frame, 1, (wxALL & ~wxRIGHT) | wxEXPAND, 5);
|
||||
main_sizer->Add(right_sizer, 0, wxALIGN_LEFT, 5);
|
||||
|
||||
wxSizer* dialog_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
dialog_sizer->Add(main_sizer, wxSizerFlags(1).Border(wxALL & ~wxBOTTOM).Expand());
|
||||
dialog_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), wxSizerFlags().Right().Border());
|
||||
SetSizerAndFit(dialog_sizer);
|
||||
SetSize(1024, 700);
|
||||
CenterOnParent();
|
||||
|
||||
Bind(wxEVT_BUTTON, &DialogAlignToVideo::process, this, wxID_OK);
|
||||
Bind(wxEVT_LEFT_DCLICK, &DialogAlignToVideo::process, this, preview_frame->GetId());
|
||||
SetIcon(GETICON(button_align_16));
|
||||
if (maximized)
|
||||
wxDialog::Maximize(true);
|
||||
}
|
||||
|
||||
DialogAlignToVideo::~DialogAlignToVideo()
|
||||
{
|
||||
long lt;
|
||||
if (!selected_tolerance->GetValue().ToLong(<))
|
||||
return;
|
||||
if (lt < 0 || lt > 255)
|
||||
return;
|
||||
|
||||
OPT_SET("Tool/Align to Video/Tolerance")->SetInt(lt);
|
||||
}
|
||||
|
||||
void rgb2lab(unsigned char r, unsigned char g, unsigned char b, double* lab)
|
||||
{
|
||||
double R = static_cast<double>(r) / 255.0;
|
||||
double G = static_cast<double>(g) / 255.0;
|
||||
double B = static_cast<double>(b) / 255.0;
|
||||
double X = 0.412453 * R + 0.357580 * G + 0.180423 * B;
|
||||
double Y = 0.212671 * R + 0.715160 * G + 0.072169 * B;
|
||||
double Z = 0.019334 * R + 0.119193 * G + 0.950227 * B;
|
||||
double xr = X / 0.950456, yr = Y / 1.000, zr = Z / 1.088854;
|
||||
|
||||
if (yr > 0.008856) {
|
||||
lab[0] = 116.0 * pow(yr, 1.0 / 3.0) - 16.0;
|
||||
}
|
||||
else {
|
||||
lab[0] = 903.3 * yr;
|
||||
}
|
||||
|
||||
double fxr, fyr, fzr;
|
||||
if (xr > 0.008856)
|
||||
fxr = pow(xr, 1.0 / 3.0);
|
||||
else
|
||||
fxr = 7.787 * xr + 16.0 / 116.0;
|
||||
|
||||
if (yr > 0.008856)
|
||||
fyr = pow(yr, 1.0 / 3.0);
|
||||
else
|
||||
fyr = 7.787 * yr + 16.0 / 116.0;
|
||||
|
||||
if (zr > 0.008856)
|
||||
fzr = pow(zr, 1.0 / 3.0);
|
||||
else
|
||||
fzr = 7.787 * zr + 16.0 / 116.0;
|
||||
|
||||
lab[1] = 500.0 * (fxr - fyr);
|
||||
lab[2] = 200.0 * (fyr - fzr);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool check_point(boost::gil::pixel<unsigned char, T> & pixel, double orig[3], unsigned char tolerance)
|
||||
{
|
||||
double lab[3];
|
||||
// in pixel: B,G,R
|
||||
rgb2lab(pixel[2], pixel[1], pixel[0], lab);
|
||||
auto diff = sqrt(pow(lab[0] - orig[0], 2) + pow(lab[1] - orig[1], 2) + pow(lab[2] - orig[2], 2));
|
||||
return diff < tolerance;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool calculate_point(boost::gil::image_view<T> view, int x, int y, double orig[3], unsigned char tolerance, int* ret)
|
||||
{
|
||||
auto origin = *view.at(x, y);
|
||||
if (!check_point(origin, orig, tolerance))
|
||||
return false;
|
||||
auto w = view.width();
|
||||
auto h = view.height();
|
||||
int l = x, r = x, u = y, d = y;
|
||||
for (int i = x + 1; i < w; i++)
|
||||
{
|
||||
auto p = *view.at(i, y);
|
||||
if (!check_point(p, orig, tolerance))
|
||||
{
|
||||
r = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = x - 1; i >= 0; i--)
|
||||
{
|
||||
auto p = *view.at(i, y);
|
||||
if (!check_point(p, orig, tolerance))
|
||||
{
|
||||
l = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = y + 1; i < h; i++)
|
||||
{
|
||||
auto p = *view.at(x, i);
|
||||
if (!check_point(p, orig, tolerance))
|
||||
{
|
||||
d = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = y - 1; i >= 0; i--)
|
||||
{
|
||||
auto p = *view.at(x, i);
|
||||
if (!check_point(p, orig, tolerance))
|
||||
{
|
||||
u = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret[0] = l;
|
||||
ret[1] = r;
|
||||
ret[2] = u;
|
||||
ret[3] = d;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DialogAlignToVideo::process(wxEvent &)
|
||||
{
|
||||
auto n_frames = provider->GetFrameCount();
|
||||
auto w = provider->GetWidth();
|
||||
auto h = provider->GetHeight();
|
||||
|
||||
long lx, ly, lt;
|
||||
if (!selected_x->GetValue().ToLong(&lx) || !selected_y->GetValue().ToLong(&ly) || !selected_tolerance->GetValue().ToLong(<))
|
||||
{
|
||||
wxMessageBox(_("Bad x or y position or tolerance value!"));
|
||||
return;
|
||||
}
|
||||
if (lx < 0 || ly < 0 || lx >= w || ly >= h)
|
||||
{
|
||||
wxMessageBox(wxString::Format(_("Bad x or y position! Require: 0 <= x < %i, 0 <= y < %i"), w, h));
|
||||
return;
|
||||
}
|
||||
if (lt < 0 || lt > 255)
|
||||
{
|
||||
wxMessageBox(_("Bad tolerance value! Require: 0 <= torlerance <= 255"));
|
||||
return;
|
||||
}
|
||||
int x = int(lx), y = int(ly);
|
||||
unsigned char tolerance = (unsigned char)(lt);
|
||||
|
||||
auto color = selected_color->GetColor();
|
||||
auto r = color.r;
|
||||
auto b = color.b;
|
||||
auto g = color.g;
|
||||
double lab[3];
|
||||
rgb2lab(r, g, b, lab);
|
||||
|
||||
int pos = current_n_frame;
|
||||
auto frame = provider->GetFrame(pos, -1, true);
|
||||
auto view = interleaved_view(frame->width, frame->height, reinterpret_cast<boost::gil::bgra8_pixel_t*>(frame->data.data()), frame->pitch);
|
||||
if (frame->flipped)
|
||||
y = frame->height - y;
|
||||
int lrud[4];
|
||||
calculate_point(view, x, y, lab, tolerance, lrud);
|
||||
|
||||
// find forward
|
||||
#define CHECK_EXISTS_POS check_exists(pos, x, y, lrud, lab, tolerance)
|
||||
while (pos >= 0)
|
||||
{
|
||||
if (CHECK_EXISTS_POS)
|
||||
pos -= 2;
|
||||
else break;
|
||||
}
|
||||
pos++;
|
||||
pos = std::max(0, pos);
|
||||
auto left = CHECK_EXISTS_POS ? pos : pos + 1;
|
||||
|
||||
pos = current_n_frame;
|
||||
while (pos < n_frames)
|
||||
{
|
||||
if (CHECK_EXISTS_POS)
|
||||
pos += 2;
|
||||
else break;
|
||||
}
|
||||
pos--;
|
||||
pos = std::min(pos, n_frames - 1);
|
||||
auto right = CHECK_EXISTS_POS ? pos : pos - 1;
|
||||
|
||||
auto timecode = context->project->Timecodes();
|
||||
auto line = context->selectionController->GetActiveLine();
|
||||
line->Start = timecode.TimeAtFrame(left, agi::vfr::Time::START);
|
||||
line->End = timecode.TimeAtFrame(right, agi::vfr::Time::END); // exclusive
|
||||
context->ass->Commit(_("Align to video by key point"), AssFile::COMMIT_DIAG_TIME);
|
||||
Close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool DialogAlignToVideo::check_exists(int pos, int x, int y, int* lrud, double* orig, unsigned char tolerance)
|
||||
{
|
||||
auto frame = provider->GetFrame(pos, -1, true);
|
||||
auto view = interleaved_view(frame->width, frame->height, reinterpret_cast<boost::gil::bgra8_pixel_t*>(frame->data.data()), frame->pitch);
|
||||
if (frame->flipped)
|
||||
y = frame->height - y;
|
||||
int actual[4];
|
||||
if (!calculate_point(view, x, y, orig, tolerance, actual)) return false;
|
||||
int dl = abs(actual[0] - lrud[0]);
|
||||
int dr = abs(actual[1] - lrud[1]);
|
||||
int du = abs(actual[2] - lrud[2]);
|
||||
int dd = abs(actual[3] - lrud[3]);
|
||||
|
||||
return dl <= 5 && dr <= 5 && du <= 5 && dd <= 5;
|
||||
}
|
||||
|
||||
void DialogAlignToVideo::update_from_textbox()
|
||||
{
|
||||
long lx, ly;
|
||||
int w = preview_image.GetWidth(), h = preview_image.GetHeight();
|
||||
if (!selected_x->GetValue().ToLong(&lx) || !selected_y->GetValue().ToLong(&ly))
|
||||
return;
|
||||
|
||||
if (lx < 0 || ly < 0 || lx >= w || ly >= h)
|
||||
return;
|
||||
int x = int(lx);
|
||||
int y = int(ly);
|
||||
auto r = preview_image.GetRed(x, y);
|
||||
auto g = preview_image.GetGreen(x, y);
|
||||
auto b = preview_image.GetBlue(x, y);
|
||||
selected_color->SetColor(agi::Color(r, g, b));
|
||||
}
|
||||
|
||||
void DialogAlignToVideo::update_from_textbox(wxCommandEvent & evt)
|
||||
{
|
||||
update_from_textbox();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void ShowAlignToVideoDialog(agi::Context * c)
|
||||
{
|
||||
c->dialog->Show<DialogAlignToVideo>(c);
|
||||
}
|
|
@ -143,7 +143,7 @@ void DialogAutosave::Populate(std::map<wxString, AutosaveFile> &files_map, std::
|
|||
|
||||
auto it = files_map.find(name);
|
||||
if (it == files_map.end())
|
||||
it = files_map.insert({name, AutosaveFile{name}}).first;
|
||||
it = files_map.insert({name, AutosaveFile{name, std::vector<Version>()}}).first;
|
||||
it->second.versions.push_back(Version{wxFileName(directory, fn).GetFullPath(), date, agi::wxformat(name_fmt, date.Format())});
|
||||
} while (dir.GetNext(&fn));
|
||||
}
|
||||
|
|
|
@ -557,9 +557,13 @@ DialogColorPicker::DialogColorPicker(wxWindow *parent, agi::Color initial_color,
|
|||
wxString modes[] = { _("RGB/R"), _("RGB/G"), _("RGB/B"), _("HSL/L"), _("HSV/H") };
|
||||
colorspace_choice = new wxChoice(this, -1, wxDefaultPosition, wxDefaultSize, 5, modes);
|
||||
|
||||
wxSize colorinput_size = GetTextExtent(" &H10117B& ");
|
||||
colorinput_size.SetHeight(-1);
|
||||
wxSize colorinput_labelsize(40, -1);
|
||||
ass_input = new wxTextCtrl(this, -1);
|
||||
#if wxCHECK_VERSION(3, 1, 3)
|
||||
wxSize colorinput_size = ass_input->GetSizeFromText(wxS("&H10117B&"));
|
||||
#else
|
||||
wxSize colorinput_size = ass_input->GetSizeFromTextSize(GetTextExtent(wxS("&H10117B&")));
|
||||
#endif
|
||||
ass_input->SetInitialSize(colorinput_size);
|
||||
|
||||
wxSizer *rgb_box = new wxStaticBoxSizer(wxHORIZONTAL, this, _("RGB color"));
|
||||
wxSizer *hsl_box = new wxStaticBoxSizer(wxVERTICAL, this, _("HSL color"));
|
||||
|
@ -568,7 +572,7 @@ DialogColorPicker::DialogColorPicker(wxWindow *parent, agi::Color initial_color,
|
|||
for (auto& elem : rgb_input)
|
||||
elem = new wxSpinCtrl(this, -1, "", wxDefaultPosition, colorinput_size, wxSP_ARROW_KEYS, 0, 255);
|
||||
|
||||
ass_input = new wxTextCtrl(this, -1, "", wxDefaultPosition, colorinput_size);
|
||||
// ass_input = new wxTextCtrl(this, -1, "", wxDefaultPosition, colorinput_size);
|
||||
html_input = new wxTextCtrl(this, -1, "", wxDefaultPosition, colorinput_size);
|
||||
alpha_input = new wxSpinCtrl(this, -1, "", wxDefaultPosition, colorinput_size, wxSP_ARROW_KEYS, 0, 255);
|
||||
|
||||
|
|
|
@ -72,19 +72,22 @@ static ResolutionShortcut resolutions[] = {
|
|||
{"704x396 (SD widescreen)", 704, 396},
|
||||
{"640x352 (SD widescreen MOD16)", 640, 352},
|
||||
{"704x400 (SD widescreen MOD16)", 704, 400},
|
||||
{"1024x576 (SuperPAL widescreen)", 1024, 576},
|
||||
{"1280x720 (HD 720p)", 1280, 720},
|
||||
{"1920x1080 (HD 1080p)", 1920, 1080},
|
||||
{"1024x576 (SuperPAL widescreen)", 1024, 576}
|
||||
{"1920x1080 (FHD 1080p)", 1920, 1080},
|
||||
{"2560x1440 (QHD 1440p)", 2560, 1440},
|
||||
{"3840x2160 (4K UHD 2160p)", 3840, 2160},
|
||||
};
|
||||
|
||||
wxSpinCtrl *spin_ctrl(wxWindow *parent, int min, int max, int *value) {
|
||||
auto ctrl = new wxSpinCtrl(parent, -1, "", wxDefaultPosition, wxSize(50, -1), wxSP_ARROW_KEYS, min, max, *value);
|
||||
auto ctrl = new wxSpinCtrl(parent, -1, "", wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max, *value);
|
||||
ctrl->SetValidator(wxGenericValidator(value));
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
// FIXME: change the misleading function name, this is TextCtrl in fact
|
||||
wxControl *spin_ctrl(wxWindow *parent, double min, double max, double *value) {
|
||||
return new wxTextCtrl(parent, -1, "", wxDefaultPosition, wxSize(50, -1), 0, DoubleValidator(value, min, max));
|
||||
return new wxTextCtrl(parent, -1, "", wxDefaultPosition, wxDefaultSize, 0, DoubleValidator(value, min, max));
|
||||
}
|
||||
|
||||
wxComboBox *resolution_shortcuts(wxWindow *parent, int width, int height) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue