Compare commits
8 commits
Author | SHA1 | Date | |
---|---|---|---|
Mia Herkt | bf532779a2 | ||
Mia Herkt | a3024815a0 | ||
Mia Herkt | 774ff2bbaf | ||
Mia Herkt | d2b3e38565 | ||
Mia Herkt | 9b3fa75ab2 | ||
Mia Herkt | 45fbd74f12 | ||
Mia Herkt | 54d8bde136 | ||
Mia Herkt | cb1157a308 |
118
.github/workflows/ci.yml
vendored
118
.github/workflows/ci.yml
vendored
|
@ -10,7 +10,6 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- feature
|
- feature
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
@ -35,37 +34,12 @@ jobs:
|
||||||
-Dharfbuzz:gobject=disabled
|
-Dharfbuzz:gobject=disabled
|
||||||
-Dharfbuzz:tests=disabled
|
-Dharfbuzz:tests=disabled
|
||||||
-Dharfbuzz:docs=disabled
|
-Dharfbuzz:docs=disabled
|
||||||
-Dharfbuzz:icu=disabled
|
|
||||||
-Dfribidi:tests=false
|
-Dfribidi:tests=false
|
||||||
-Dfribidi:docs=false
|
-Dfribidi:docs=false
|
||||||
-Dlibass:fontconfig=disabled
|
-Dlibass:fontconfig=disabled
|
||||||
-Dffmpeg:libdav1d=enabled
|
|
||||||
-Davisynth=enabled
|
-Davisynth=enabled
|
||||||
-Dbestsource=enabled
|
-Dbestsource=enabled
|
||||||
-Dvapoursynth=enabled
|
-Dvapoursynth=enabled
|
||||||
- name: Windows MSVC Release (wx master)
|
|
||||||
os: windows-latest
|
|
||||||
msvc: true
|
|
||||||
buildtype: release
|
|
||||||
args: >-
|
|
||||||
-Ddefault_library=static
|
|
||||||
--force-fallback-for=zlib,harfbuzz,freetype2,fribidi,libpng
|
|
||||||
-Dfreetype2:harfbuzz=disabled
|
|
||||||
-Dharfbuzz:freetype=disabled
|
|
||||||
-Dharfbuzz:cairo=disabled
|
|
||||||
-Dharfbuzz:glib=disabled
|
|
||||||
-Dharfbuzz:gobject=disabled
|
|
||||||
-Dharfbuzz:tests=disabled
|
|
||||||
-Dharfbuzz:docs=disabled
|
|
||||||
-Dharfbuzz:icu=disabled
|
|
||||||
-Dfribidi:tests=false
|
|
||||||
-Dfribidi:docs=false
|
|
||||||
-Dlibass:fontconfig=disabled
|
|
||||||
-Dffmpeg:libdav1d=enabled
|
|
||||||
-Davisynth=enabled
|
|
||||||
-Dbestsource=enabled
|
|
||||||
-Dvapoursynth=enabled
|
|
||||||
-Dwx_version='3.3.0'
|
|
||||||
#- {
|
#- {
|
||||||
# name: Windows MinGW,
|
# name: Windows MinGW,
|
||||||
# os: windows-latest,
|
# os: windows-latest,
|
||||||
|
@ -83,39 +57,25 @@ jobs:
|
||||||
buildtype: release,
|
buildtype: release,
|
||||||
args: ''
|
args: ''
|
||||||
}
|
}
|
||||||
- name: Ubuntu AppImage
|
|
||||||
os: ubuntu-22.04
|
|
||||||
buildtype: release
|
|
||||||
appimage: true
|
|
||||||
# distro ffms is currently broken
|
|
||||||
args: >-
|
|
||||||
--prefix=/usr
|
|
||||||
-Dbuild_appimage=true
|
|
||||||
-Ddefault_library=static
|
|
||||||
--force-fallback-for=ffms2
|
|
||||||
-Dffmpeg:libdav1d=enabled
|
|
||||||
-Davisynth=enabled
|
|
||||||
-Dbestsource=enabled
|
|
||||||
-Dvapoursynth=enabled
|
|
||||||
- {
|
- {
|
||||||
name: macOS Release,
|
name: macOS Release,
|
||||||
os: macos-13,
|
os: macos-latest,
|
||||||
buildtype: release,
|
buildtype: release,
|
||||||
args: -Ddefault_library=static -Dbuild_osx_bundle=true -Dlocal_boost=true -Dvapoursynth=enabled --force-fallback-for=ffms2
|
args: -Ddefault_library=static -Dbuild_osx_bundle=true -Dlocal_boost=true
|
||||||
}
|
}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: '0'
|
fetch-depth: '0'
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: '3.x'
|
||||||
|
|
||||||
- name: Setup Meson
|
- name: Setup Meson
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip setuptools
|
python -m pip install --upgrade pip
|
||||||
pip install meson
|
pip install meson
|
||||||
|
|
||||||
- name: Setup MSVC
|
- name: Setup MSVC
|
||||||
|
@ -126,45 +86,31 @@ jobs:
|
||||||
if: matrix.config.os == 'windows-latest'
|
if: matrix.config.os == 'windows-latest'
|
||||||
run: |
|
run: |
|
||||||
choco install ninja innosetup
|
choco install ninja innosetup
|
||||||
|
$url = "https://github.com/leafo/moonscript/releases/download/win32-v0.5.0/moonscript-187bac54ee5a7450013e9c38e005a0e671b76f45.zip"
|
||||||
$moonscripturl = "https://github.com/leafo/moonscript/releases/download/win32-v0.5.0/moonscript-187bac54ee5a7450013e9c38e005a0e671b76f45.zip"
|
|
||||||
mkdir moonscript
|
mkdir moonscript
|
||||||
Invoke-WebRequest -Uri $moonscripturl -OutFile ".\moonscript\moonscript.zip"
|
Invoke-WebRequest -Uri $url -OutFile ".\moonscript\moonscript.zip"
|
||||||
pushd moonscript
|
cd moonscript
|
||||||
7z e moonscript.zip
|
7z e moonscript.zip
|
||||||
Get-Location | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
Get-Location | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
popd
|
|
||||||
|
|
||||||
$gettexturl = "https://github.com/mlocati/gettext-iconv-windows/releases/download/v0.21-v1.16/gettext0.21-iconv1.16-static-64.zip"
|
|
||||||
Invoke-WebRequest -Uri $gettexturl -OutFile ".\gettext.zip"
|
|
||||||
Expand-Archive ".\gettext.zip" -DestinationPath gettext
|
|
||||||
pushd gettext/bin
|
|
||||||
Get-Location | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
|
||||||
popd
|
|
||||||
|
|
||||||
- name: Install dependencies (MacOS)
|
- name: Install dependencies (MacOS)
|
||||||
if: startsWith(matrix.config.os, 'macos-')
|
if: matrix.config.os == 'macos-latest'
|
||||||
run: |
|
run: |
|
||||||
export HOMEBREW_NO_INSTALL_CLEANUP=1
|
brew update
|
||||||
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
|
|
||||||
# Skip brew update for now, see https://github.com/actions/setup-python/issues/577
|
|
||||||
# brew update
|
|
||||||
brew install luarocks ninja
|
brew install luarocks ninja
|
||||||
sudo luarocks install luafilesystem 1.8.0
|
luarocks install luafilesystem 1.8.0
|
||||||
sudo luarocks install moonscript --dev
|
luarocks install moonscript --dev
|
||||||
brew install libass zlib ffms2 fftw hunspell
|
brew install libass zlib ffms2 fftw hunspell
|
||||||
brew install pulseaudio # NO OpenAL in github CI
|
brew install pulseaudio # NO OpenAL in github CI
|
||||||
|
|
||||||
- name: Install dependencies (Linux)
|
- name: Install dependencies (Linux)
|
||||||
if: startsWith(matrix.config.os, 'ubuntu-')
|
if: matrix.config.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install ninja-build build-essential libx11-dev libwxgtk3.0-gtk3-dev libfreetype6-dev pkg-config libfontconfig1-dev libass-dev libasound2-dev libffms2-dev intltool libboost-all-dev libhunspell-dev libuchardet-dev libpulse-dev libopenal-dev libxxhash-dev nasm
|
sudo apt-get install ninja-build build-essential libx11-dev libwxgtk3.0-gtk3-dev libfreetype6-dev pkg-config libfontconfig1-dev libass-dev libasound2-dev libffms2-dev intltool libboost-all-dev
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: |
|
run: meson build ${{ matrix.config.args }} -Dbuildtype=${{ matrix.config.buildtype }}
|
||||||
meson setup build ${{ matrix.config.args }} -Dbuildtype=${{ matrix.config.buildtype }}
|
|
||||||
meson configure build
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: meson compile -C build
|
run: meson compile -C build
|
||||||
|
@ -185,7 +131,7 @@ jobs:
|
||||||
run: cd build && ninja win-portable
|
run: cd build && ninja win-portable
|
||||||
|
|
||||||
- name: Upload artifacts - win_installer
|
- name: Upload artifacts - win_installer
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
if: matrix.config.os == 'windows-latest'
|
if: matrix.config.os == 'windows-latest'
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.config.name }} - installer
|
name: ${{ matrix.config.name }} - installer
|
||||||
|
@ -193,7 +139,7 @@ jobs:
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload artifacts - portable.zip
|
- name: Upload artifacts - portable.zip
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
if: matrix.config.os == 'windows-latest'
|
if: matrix.config.os == 'windows-latest'
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.config.name }} - portable
|
name: ${{ matrix.config.name }} - portable
|
||||||
|
@ -201,39 +147,15 @@ jobs:
|
||||||
|
|
||||||
# macOS artifacts
|
# macOS artifacts
|
||||||
- name: Generate macOS installer
|
- name: Generate macOS installer
|
||||||
if: startsWith(matrix.config.os, 'macos-')
|
if: matrix.config.os == 'macos-latest'
|
||||||
run: |
|
run: |
|
||||||
meson compile osx-bundle -C build
|
meson compile osx-bundle -C build
|
||||||
meson compile osx-build-dmg -C build
|
meson compile osx-build-dmg -C build
|
||||||
|
|
||||||
- name: Upload artifacts - macOS dmg
|
- name: Upload artifacts - macOS dmg
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
if: startsWith(matrix.config.os, 'macos-')
|
if: matrix.config.os == 'macos-latest'
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.config.name }} - installer
|
name: ${{ matrix.config.name }} - installer
|
||||||
path: build/Aegisub-*.dmg
|
path: build/Aegisub-*.dmg
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
# Linux artifacts (AppImage)
|
|
||||||
- name: Generate AppImage
|
|
||||||
if: matrix.config.appimage
|
|
||||||
run: |
|
|
||||||
mkdir -p appimage/appdir
|
|
||||||
meson install -C build --destdir=../appimage/appdir
|
|
||||||
|
|
||||||
cd appimage
|
|
||||||
sudo apt-get install libfuse2
|
|
||||||
curl -L "https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20220822-1/linuxdeploy-x86_64.AppImage" -o linuxdeploy
|
|
||||||
curl -L "https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage" -o appimagetool
|
|
||||||
chmod +x linuxdeploy appimagetool
|
|
||||||
|
|
||||||
./linuxdeploy --appdir appdir --desktop-file=appdir/aegisub.desktop
|
|
||||||
./appimagetool appdir
|
|
||||||
|
|
||||||
- name: Upload artifacts - Linux AppImage
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
if: matrix.config.appimage
|
|
||||||
with:
|
|
||||||
name: ${{ matrix.config.name }}
|
|
||||||
path: appimage/*.AppImage
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -33,6 +33,7 @@ subprojects/glib*
|
||||||
subprojects/googletest-*
|
subprojects/googletest-*
|
||||||
subprojects/harfbuzz
|
subprojects/harfbuzz
|
||||||
subprojects/icu
|
subprojects/icu
|
||||||
|
subprojects/jansson
|
||||||
subprojects/libass
|
subprojects/libass
|
||||||
subprojects/libffi*
|
subprojects/libffi*
|
||||||
subprojects/libpng-*
|
subprojects/libpng-*
|
||||||
|
@ -46,4 +47,3 @@ subprojects/dirent-*
|
||||||
subprojects/hunspell-*
|
subprojects/hunspell-*
|
||||||
subprojects/uchardet-*
|
subprojects/uchardet-*
|
||||||
subprojects/vapoursynth
|
subprojects/vapoursynth
|
||||||
subprojects/xxhash
|
|
||||||
|
|
23
README.md
23
README.md
|
@ -1,7 +1,5 @@
|
||||||
## arch1t3cht's Aegisub "fork"
|
## arch1t3cht's Aegisub "fork"
|
||||||
Download release builds [here](https://github.com/arch1t3cht/Aegisub/releases), or the latest CI builds [here](https://github.com/arch1t3cht/Aegisub/actions?query=branch%3Afeature+event%3Apush).
|
Go [here](#branchfeature-list) for the new features.
|
||||||
|
|
||||||
The release page also has detailed list of all changes and new features. If you're interested in the technical details or want to compile yourself, read on.
|
|
||||||
|
|
||||||
### Don't we have enough Aegisub forks already??
|
### Don't we have enough Aegisub forks already??
|
||||||
We absolutely do, and I'm aware that adding another one [doesn't sound like](https://xkcd.com/927/) a [good idea on paper](https://cdn.discordapp.com/attachments/425357202963038208/1007103606421459004/unknown.png). However,
|
We absolutely do, and I'm aware that adding another one [doesn't sound like](https://xkcd.com/927/) a [good idea on paper](https://cdn.discordapp.com/attachments/425357202963038208/1007103606421459004/unknown.png). However,
|
||||||
|
@ -52,6 +50,9 @@ This is probably because you're building with wxgtk2. Building with wxgtk3 fixes
|
||||||
|
|
||||||
The exact way of switching depends on your Linux distribution, but essentially you need to ensure that `wx-config` or the next best variant of it points to wxgtk3. If it points to wxgtk2 by default and deinstalling wxgtk2 isn't an option, you can also temporarily move it out of the path or use a `native-file` in your meson project. Then, fully reconfigure meson using `meson configure --clearcache` and `meson setup --reconfigure`.
|
The exact way of switching depends on your Linux distribution, but essentially you need to ensure that `wx-config` or the next best variant of it points to wxgtk3. If it points to wxgtk2 by default and deinstalling wxgtk2 isn't an option, you can also temporarily move it out of the path or use a `native-file` in your meson project. Then, fully reconfigure meson using `meson configure --clearcache` and `meson setup --reconfigure`.
|
||||||
|
|
||||||
|
#### I get errors like "Option not found" after merging one of these branches
|
||||||
|
The changes to `default_config.json` or similar files weren't detected by meson due to missing regen dependencies. You can either merge the `bugfixes` branch or rebuild from scratch.
|
||||||
|
|
||||||
#### The video is desynced / Frames don't appear at the right time
|
#### The video is desynced / Frames don't appear at the right time
|
||||||
This is probably due to the ffms2 seeking bug ([#394](https://github.com/FFMS/ffms2/issues/394)). On Windows, this specific regression shouldn't happen anymore. On Linux, you need to install the latest git version of ffms2 - for example the [`ffms2-git`](https://aur.archlinux.org/packages/ffms2-git) AUR package on Arch linux, or just compile it yourself.
|
This is probably due to the ffms2 seeking bug ([#394](https://github.com/FFMS/ffms2/issues/394)). On Windows, this specific regression shouldn't happen anymore. On Linux, you need to install the latest git version of ffms2 - for example the [`ffms2-git`](https://aur.archlinux.org/packages/ffms2-git) AUR package on Arch linux, or just compile it yourself.
|
||||||
|
|
||||||
|
@ -62,13 +63,11 @@ If you're compiling yourself, try adding `--force-fallback-for=zlib` to the meso
|
||||||
|
|
||||||
|
|
||||||
### Compilation
|
### Compilation
|
||||||
If you're just looking to install Aegisub, you might want to check out the [releases page](https://github.com/arch1t3cht/Aegisub/releases) or the [CI builds](https://github.com/arch1t3cht/Aegisub/actions) first.
|
|
||||||
|
|
||||||
For compilation on Windows, see the TSTools documentation below. Also check the [GitHub workflow](https://github.com/arch1t3cht/Aegisub/blob/cibuilds/.github/workflows/ci.yml) for the project arguments.
|
For compilation on Windows, see the TSTools documentation below. Also check the [GitHub workflow](https://github.com/arch1t3cht/Aegisub/blob/cibuilds/.github/workflows/ci.yml) for the project arguments.
|
||||||
|
|
||||||
On Arch Linux, there is an AUR package called [aegisub-arch1t3cht-git](https://aur.archlinux.org/packages/aegisub-arch1t3cht-git). It's not maintained by me but seems to work.
|
On Arch Linux, there is an AUR package called [aegisub-arch1t3cht-git](https://aur.archlinux.org/packages/aegisub-arch1t3cht-git). It's not maintained by me but seems to work.
|
||||||
|
|
||||||
On other Linux distributions or for manual compilation you can use this package or the [TSTools PKGBUILD](https://aur.archlinux.org/packages/aegisub-ttools-meson-git) as a reference, in particular for installing the necessary dependencies if you don't want to compile them yourself.
|
On other distributions or for manual compilation you can use this package or the [TSTools PKGBUILD](https://aur.archlinux.org/packages/aegisub-ttools-meson-git) as a reference, in particular for installing the necessary dependencies if you don't want to compile them yourself.
|
||||||
If all dependencies are installed:
|
If all dependencies are installed:
|
||||||
- Install Meson
|
- Install Meson
|
||||||
- Clone the repository
|
- Clone the repository
|
||||||
|
@ -90,14 +89,14 @@ To change the options of an existing build directory, run `meson setup --reconfi
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
Apart from the dependencies for the TSTools version, there are some additional dependencies. These are cloned and compiled from scratch if not found, but you might want to install binaries instead:
|
Apart from the dependencies for the TSTools version, there are some additional dependencies. These are cloned and compiled from scratch if not found, but you might want to install binaries instead:
|
||||||
- `xxhash`: For BestSource
|
- `jansson`: For BestSource
|
||||||
- `ffmpeg`: Becomes a direct dependency when compiling with BestSource
|
- `ffmpeg`: Becomes a direct dependency when compiling with BestSource
|
||||||
- `avisynth` (or `avisynthplus`): Optional run-time dependency for the Avisynth audio/video provider
|
- `avisynth` (or `avisynthplus`): Optional run-time dependency for the Avisynth source
|
||||||
- `vapoursynth`: Optional run-time dependency for the VapourSynth audio/video provider
|
- `vapoursynth`: Optional run-time dependency for the VapourSynth source
|
||||||
|
|
||||||
The following VapourSynth plugins are used by the default scripts set in the default configuration:
|
The following VapourSynth plugins are used by the default scripts set in the default configuration:
|
||||||
- [`lsmas`](https://github.com/HomeOfAviSynthPlusEvolution/L-SMASH-Works): For LWLibavSource
|
- [`lsmas`](https://github.com/AkarinVS/L-SMASH-Works): For LWLibavSource
|
||||||
- [`bs`](https://github.com/vapoursynth/bestsource): For BestSource, used for audio
|
- [`bas`](https://github.com/vapoursynth/bestaudiosource): For BestAudioSource
|
||||||
- [`wwxd`](https://github.com/dubhater/vapoursynth-wwxd) and [`scxvid`](https://github.com/dubhater/vapoursynth-scxvid) (depending on settings): For keyframe generation
|
- [`wwxd`](https://github.com/dubhater/vapoursynth-wwxd) and [`scxvid`](https://github.com/dubhater/vapoursynth-scxvid) (depending on settings): For keyframe generation
|
||||||
|
|
||||||
|
|
||||||
|
@ -133,7 +132,7 @@ All other dependencies are either stored in the repository or are included as su
|
||||||
|
|
||||||
Building:
|
Building:
|
||||||
|
|
||||||
1. Clone Aegisub's repository: `git clone https://github.com/arch1t3cht/Aegisub.git`
|
1. Clone Aegisub's repository: `git clone https://github.com/TypesettingTools/Aegisub.git`
|
||||||
2. From the Visual Studio "x64 Native Tools Command Prompt", generate the build directory: `meson build -Ddefault_library=static` (if building for release, add `--buildtype=release`)
|
2. From the Visual Studio "x64 Native Tools Command Prompt", generate the build directory: `meson build -Ddefault_library=static` (if building for release, add `--buildtype=release`)
|
||||||
3. Build with `cd build` and `ninja`
|
3. Build with `cd build` and `ninja`
|
||||||
|
|
||||||
|
|
|
@ -23,10 +23,10 @@ OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
cleantags_version = "1.302"
|
cleantags_version = "1.301"
|
||||||
cleantags_modified = "12 October 2023"
|
cleantags_modified = "13 November 2009"
|
||||||
|
|
||||||
ktag = "\\[kK][fo]?[%d.]+"
|
ktag = "\\[kK][fo]?%d+"
|
||||||
|
|
||||||
--[[ The main function that performs the cleaning up
|
--[[ The main function that performs the cleaning up
|
||||||
Takes: text
|
Takes: text
|
||||||
|
|
|
@ -100,7 +100,7 @@ function io.open(fname, mode)
|
||||||
local file = assert(orig_open("nul", "rb"))
|
local file = assert(orig_open("nul", "rb"))
|
||||||
if ffi.C._wfreopen(wfname, wmode, file) == nil then
|
if ffi.C._wfreopen(wfname, wmode, file) == nil then
|
||||||
local msg, errno = select(2, file:close())
|
local msg, errno = select(2, file:close())
|
||||||
return nil, fname .. ": " .. tostring(msg), errno
|
return nil, fname .. ": " .. msg, errno
|
||||||
end
|
end
|
||||||
|
|
||||||
return file
|
return file
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
subdir('include')
|
subdir('include')
|
||||||
subdir('vapoursynth')
|
|
||||||
|
|
||||||
automation_dir = dataroot / 'automation'
|
automation_dir = dataroot / 'automation'
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ Returns: number
|
||||||
|
|
||||||
Get RGB pixel value at a certain position of frame object.
|
Get RGB pixel value at a certain position of frame object.
|
||||||
|
|
||||||
function frame:getPixel(x, y)
|
function frame:frame:getPixel(x, y)
|
||||||
|
|
||||||
@x (number)
|
@x (number)
|
||||||
Pixel to retrieve on the x-axis
|
Pixel to retrieve on the x-axis
|
||||||
|
@ -48,10 +48,8 @@ function frame:getPixel(x, y)
|
||||||
@y (number)
|
@y (number)
|
||||||
Pixel to retrieve on the y-axis
|
Pixel to retrieve on the y-axis
|
||||||
|
|
||||||
Returns: 3 values, all numbers
|
Returns: number
|
||||||
1. R value of the pixel
|
Integer value representing the RGB pixel value.
|
||||||
2. G value of the pixel
|
|
||||||
3. B value of the pixel
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -69,25 +67,3 @@ Returns: string
|
||||||
String in ASS format representing the pixel value. e.g. "&H0073FF&"
|
String in ASS format representing the pixel value. e.g. "&H0073FF&"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Get raw BGRA (alpha being irrelevant) data of frame object, whose pixel values
|
|
||||||
can then be accessed via LuaJIT's FFI.
|
|
||||||
|
|
||||||
The frame data is valid until the frame object is garbage-collected.
|
|
||||||
|
|
||||||
Example usage (which does not account for flipped frames for simplicity)
|
|
||||||
|
|
||||||
data, pitch = frame:data()
|
|
||||||
buf = require("ffi").cast("unsigned char *", data)
|
|
||||||
-- Get the R value of the pixel at coordinates (42, 34)
|
|
||||||
pix_val = buf[34 * pitch + 4 * 42 + 2]
|
|
||||||
|
|
||||||
function frame:data()
|
|
||||||
|
|
||||||
Returns: 3 values - a lightuserdata, a number, and a boolean
|
|
||||||
1. Lightuserdata object which can be cast to "unsigned char *" via ffi.cast, a pointer
|
|
||||||
to the raw frame data.
|
|
||||||
2. The pitch of the frame data.
|
|
||||||
3. Whether the frame is flipped upside-down.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
|
@ -22,18 +22,14 @@ Aegisub using the following variables:
|
||||||
- __aegi_hasaudio: int: If nonzero, Aegisub will try to load an audio track
|
- __aegi_hasaudio: int: If nonzero, Aegisub will try to load an audio track
|
||||||
from the same file.
|
from the same file.
|
||||||
|
|
||||||
The script can control the progress dialog shown by Aegisub with certain log
|
|
||||||
messages. Check the functions defined below for more information.
|
|
||||||
|
|
||||||
This module provides some utility functions to obtain timecodes, keyframes, and
|
This module provides some utility functions to obtain timecodes, keyframes, and
|
||||||
other data.
|
other data.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
from enum import Enum
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from typing import Any, Dict, List, Tuple, Callable
|
from typing import Any, Dict, List, Tuple
|
||||||
|
|
||||||
import vapoursynth as vs
|
import vapoursynth as vs
|
||||||
core = vs.core
|
core = vs.core
|
||||||
|
@ -43,28 +39,6 @@ aegi_vsplugins: str = ""
|
||||||
|
|
||||||
plugin_extension = ".dll" if os.name == "nt" else ".so"
|
plugin_extension = ".dll" if os.name == "nt" else ".so"
|
||||||
|
|
||||||
def progress_set_message(message: str):
|
|
||||||
"""
|
|
||||||
Sets the message of Aegisub's progress dialog.
|
|
||||||
"""
|
|
||||||
vs.core.log_message(vs.MESSAGE_TYPE_DEBUG, f"__aegi_set_message,{message}")
|
|
||||||
|
|
||||||
|
|
||||||
def progress_set_progress(percent: float):
|
|
||||||
"""
|
|
||||||
Sets the progress shown in Aegisub's progress dialog to
|
|
||||||
the given percentage.
|
|
||||||
"""
|
|
||||||
vs.core.log_message(vs.MESSAGE_TYPE_DEBUG, f"__aegi_set_progress,{percent}")
|
|
||||||
|
|
||||||
|
|
||||||
def progress_set_indeterminate():
|
|
||||||
"""
|
|
||||||
Sets Aegisub's progress dialog to show indeterminate progress.
|
|
||||||
"""
|
|
||||||
vs.core.log_message(vs.MESSAGE_TYPE_DEBUG, f"__aegi_set_indeterminate,")
|
|
||||||
|
|
||||||
|
|
||||||
def set_paths(vars: dict):
|
def set_paths(vars: dict):
|
||||||
"""
|
"""
|
||||||
Initialize the wrapper library with the given configuration directories.
|
Initialize the wrapper library with the given configuration directories.
|
||||||
|
@ -123,19 +97,16 @@ def make_keyframes_filename(filename: str) -> str:
|
||||||
lwindex_re1 = re.compile(r"Index=(?P<Index>-?[0-9]+),POS=(?P<POS>-?[0-9]+),PTS=(?P<PTS>-?[0-9]+),DTS=(?P<DTS>-?[0-9]+),EDI=(?P<EDI>-?[0-9]+)")
|
lwindex_re1 = re.compile(r"Index=(?P<Index>-?[0-9]+),POS=(?P<POS>-?[0-9]+),PTS=(?P<PTS>-?[0-9]+),DTS=(?P<DTS>-?[0-9]+),EDI=(?P<EDI>-?[0-9]+)")
|
||||||
lwindex_re2 = re.compile(r"Key=(?P<Key>-?[0-9]+),Pic=(?P<Pic>-?[0-9]+),POC=(?P<POC>-?[0-9]+),Repeat=(?P<Repeat>-?[0-9]+),Field=(?P<Field>-?[0-9]+)")
|
lwindex_re2 = re.compile(r"Key=(?P<Key>-?[0-9]+),Pic=(?P<Pic>-?[0-9]+),POC=(?P<POC>-?[0-9]+),Repeat=(?P<Repeat>-?[0-9]+),Field=(?P<Field>-?[0-9]+)")
|
||||||
streaminfo_re = re.compile(r"Codec=(?P<Codec>[0-9]+),TimeBase=(?P<TimeBase>[0-9\/]+),Width=(?P<Width>[0-9]+),Height=(?P<Height>[0-9]+),Format=(?P<Format>[0-9a-zA-Z]+),ColorSpace=(?P<ColorSpace>[0-9]+)")
|
streaminfo_re = re.compile(r"Codec=(?P<Codec>[0-9]+),TimeBase=(?P<TimeBase>[0-9\/]+),Width=(?P<Width>[0-9]+),Height=(?P<Height>[0-9]+),Format=(?P<Format>[0-9a-zA-Z]+),ColorSpace=(?P<ColorSpace>[0-9]+)")
|
||||||
videoindex_re = re.compile(r"<ActiveVideoStreamIndex>(?P<VideoStreamIndex>[0-9+]+)</ActiveVideoStreamIndex>")
|
|
||||||
|
|
||||||
class LWIndexFrame:
|
class LWIndexFrame:
|
||||||
pts: int
|
pts: int
|
||||||
key: int
|
key: int
|
||||||
index: int
|
|
||||||
|
|
||||||
def __init__(self, raw: list[str]):
|
def __init__(self, raw: list[str]):
|
||||||
match1 = lwindex_re1.match(raw[0])
|
match1 = lwindex_re1.match(raw[0])
|
||||||
match2 = lwindex_re2.match(raw[1])
|
match2 = lwindex_re2.match(raw[1])
|
||||||
if not match1 or not match2:
|
if not match1 or not match2:
|
||||||
raise ValueError("Invalid lwindex format")
|
raise ValueError("Invalid lwindex format")
|
||||||
self.index = int(match1.group("Index"))
|
|
||||||
self.pts = int(match1.group("PTS"))
|
self.pts = int(match1.group("PTS"))
|
||||||
self.key = int(match2.group("Key"))
|
self.key = int(match2.group("Key"))
|
||||||
|
|
||||||
|
@ -153,19 +124,11 @@ def info_from_lwindex(indexfile: str) -> Dict[str, List[int]]:
|
||||||
with open(indexfile, encoding="latin1") as f:
|
with open(indexfile, encoding="latin1") as f:
|
||||||
index = f.read().splitlines()
|
index = f.read().splitlines()
|
||||||
|
|
||||||
videoindex_str = next(l for l in index if l.startswith("<ActiveVideoStreamIndex>"))
|
indexstart, indexend = index.index("</StreamInfo>") + 1, index.index("</LibavReaderIndex>")
|
||||||
videoindex_match = videoindex_re.match(videoindex_str)
|
|
||||||
if not videoindex_match:
|
|
||||||
raise ValueError("Invalid lwindex format: Invalid ActiveVideoStreamIndex line")
|
|
||||||
videoindex = int(videoindex_match.group("VideoStreamIndex"))
|
|
||||||
|
|
||||||
# The picture list starts after the last </StreamInfo> tag
|
|
||||||
indexstart, indexend = (len(index) - index[::-1].index("</StreamInfo>")), index.index("</LibavReaderIndex>")
|
|
||||||
frames = [LWIndexFrame(index[i:i+2]) for i in range(indexstart, indexend, 2)]
|
frames = [LWIndexFrame(index[i:i+2]) for i in range(indexstart, indexend, 2)]
|
||||||
frames = [f for f in frames if f.index == videoindex] # select the first stream
|
|
||||||
frames.sort(key=int)
|
frames.sort(key=int)
|
||||||
|
|
||||||
streaminfo = streaminfo_re.match(index[index.index(f"<StreamInfo={videoindex},0>") + 1]) # info of first stream
|
streaminfo = streaminfo_re.match(index[indexstart - 2])
|
||||||
if not streaminfo:
|
if not streaminfo:
|
||||||
raise ValueError("Invalid lwindex format")
|
raise ValueError("Invalid lwindex format")
|
||||||
|
|
||||||
|
@ -187,12 +150,12 @@ def wrap_lwlibavsource(filename: str, cachedir: str | None = None, **kwargs: Any
|
||||||
if cachedir is None:
|
if cachedir is None:
|
||||||
cachedir = aegi_vscache
|
cachedir = aegi_vscache
|
||||||
|
|
||||||
os.makedirs(cachedir, exist_ok=True)
|
try:
|
||||||
|
os.mkdir(cachedir)
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
cachefile = os.path.join(cachedir, make_lwi_cache_filename(filename))
|
cachefile = os.path.join(cachedir, make_lwi_cache_filename(filename))
|
||||||
|
|
||||||
progress_set_message("Loading video file")
|
|
||||||
progress_set_indeterminate()
|
|
||||||
|
|
||||||
ensure_plugin("lsmas", "libvslsmashsource", "To use Aegisub's LWLibavSource wrapper, the `lsmas` plugin for VapourSynth must be installed")
|
ensure_plugin("lsmas", "libvslsmashsource", "To use Aegisub's LWLibavSource wrapper, the `lsmas` plugin for VapourSynth must be installed")
|
||||||
|
|
||||||
if b"-Dcachedir" not in core.lsmas.Version()["config"]: # type: ignore
|
if b"-Dcachedir" not in core.lsmas.Version()["config"]: # type: ignore
|
||||||
|
@ -200,7 +163,6 @@ def wrap_lwlibavsource(filename: str, cachedir: str | None = None, **kwargs: Any
|
||||||
|
|
||||||
clip = core.lsmas.LWLibavSource(source=filename, cachefile=cachefile, **kwargs)
|
clip = core.lsmas.LWLibavSource(source=filename, cachefile=cachefile, **kwargs)
|
||||||
|
|
||||||
progress_set_message("Getting timecodes and keyframes from the index file")
|
|
||||||
return clip, info_from_lwindex(cachefile)
|
return clip, info_from_lwindex(cachefile)
|
||||||
|
|
||||||
|
|
||||||
|
@ -209,6 +171,7 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
|
||||||
**kwargs: Any) -> List[int]:
|
**kwargs: Any) -> List[int]:
|
||||||
"""
|
"""
|
||||||
Generates a list of keyframes from a clip, using either WWXD or Scxvid.
|
Generates a list of keyframes from a clip, using either WWXD or Scxvid.
|
||||||
|
Will be slightly more efficient with the `akarin` plugin installed.
|
||||||
|
|
||||||
:param clip: Clip to process.
|
:param clip: Clip to process.
|
||||||
:param use_scxvid: Whether to use Scxvid. If False, the function uses WWXD.
|
:param use_scxvid: Whether to use Scxvid. If False, the function uses WWXD.
|
||||||
|
@ -218,9 +181,6 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
|
||||||
The remaining keyword arguments are passed on to the respective filter.
|
The remaining keyword arguments are passed on to the respective filter.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
progress_set_message("Generating keyframes")
|
|
||||||
progress_set_progress(1)
|
|
||||||
|
|
||||||
clip = core.resize.Bilinear(clip, width=resize_h * clip.width // clip.height, height=resize_h, format=resize_format)
|
clip = core.resize.Bilinear(clip, width=resize_h * clip.width // clip.height, height=resize_h, format=resize_format)
|
||||||
|
|
||||||
if use_scxvid:
|
if use_scxvid:
|
||||||
|
@ -236,12 +196,12 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
|
||||||
nonlocal done
|
nonlocal done
|
||||||
keyframes[n] = f.props._SceneChangePrev if use_scxvid else f.props.Scenechange # type: ignore
|
keyframes[n] = f.props._SceneChangePrev if use_scxvid else f.props.Scenechange # type: ignore
|
||||||
done += 1
|
done += 1
|
||||||
if done % max(1, clip.num_frames // 200) == 0:
|
if done % (clip.num_frames // 25) == 0:
|
||||||
progress_set_progress(100 * done / clip.num_frames)
|
vs.core.log_message(vs.MESSAGE_TYPE_INFORMATION, "Detecting keyframes... {}% done.\n".format(100 * done // clip.num_frames))
|
||||||
return f
|
return f
|
||||||
|
|
||||||
deque(clip.std.ModifyFrame(clip, _cb).frames(close=True), 0)
|
deque(clip.std.ModifyFrame(clip, _cb).frames(close=True), 0)
|
||||||
progress_set_progress(100)
|
vs.core.log_message(vs.MESSAGE_TYPE_INFORMATION, "Done detecting keyframes.\n")
|
||||||
return [n for n in range(clip.num_frames) if keyframes[n]]
|
return [n for n in range(clip.num_frames) if keyframes[n]]
|
||||||
|
|
||||||
|
|
||||||
|
@ -256,49 +216,26 @@ def save_keyframes(filename: str, keyframes: List[int]):
|
||||||
f.write("".join(f"{n}\n" for n in keyframes))
|
f.write("".join(f"{n}\n" for n in keyframes))
|
||||||
|
|
||||||
|
|
||||||
class GenKeyframesMode(Enum):
|
def try_get_keyframes(filename: str, default: str | List[int]) -> str | List[int]:
|
||||||
NEVER = 0
|
|
||||||
ALWAYS = 1
|
|
||||||
ASK = 2
|
|
||||||
|
|
||||||
|
|
||||||
def ask_gen_keyframes(_: str) -> bool:
|
|
||||||
from tkinter.messagebox import askyesno
|
|
||||||
progress_set_message("Asking whether to generate keyframes")
|
|
||||||
progress_set_indeterminate()
|
|
||||||
result = askyesno("Generate Keyframes", \
|
|
||||||
"No keyframes file was found for this video file.\nShould Aegisub detect keyframes from the video?\nThis will take a while.", default="no")
|
|
||||||
progress_set_message("")
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def get_keyframes(filename: str, clip: vs.VideoNode, fallback: str | List[int],
|
|
||||||
generate: GenKeyframesMode = GenKeyframesMode.ASK,
|
|
||||||
ask_callback: Callable = ask_gen_keyframes, **kwargs: Any) -> str | List[int]:
|
|
||||||
"""
|
"""
|
||||||
Looks for a keyframes file for the given filename.
|
Checks if a keyframes file for the given filename is present and, if so,
|
||||||
If no file was found, this function can generate a keyframe file for the given clip next
|
returns it. Otherwise, returns the given list of keyframes.
|
||||||
|
"""
|
||||||
|
kffilename = make_keyframes_filename(filename)
|
||||||
|
|
||||||
|
return kffilename if os.path.exists(kffilename) else default
|
||||||
|
|
||||||
|
|
||||||
|
def get_keyframes(filename: str, clip: vs.VideoNode, **kwargs: Any) -> str:
|
||||||
|
"""
|
||||||
|
When not already present, creates a keyframe file for the given clip next
|
||||||
to the given filename using WWXD or Scxvid (see the make_keyframes docstring).
|
to the given filename using WWXD or Scxvid (see the make_keyframes docstring).
|
||||||
Whether or not keyframes are generated depends on the `generate` argument.
|
|
||||||
Depending on the `generate` argument, the function will
|
|
||||||
- always generate keyframes when no file was found
|
|
||||||
- never generate keyframes when no file was found
|
|
||||||
(and return the fallback keyframes instead)
|
|
||||||
- show a dialog to ask the user whether keyframes should be
|
|
||||||
generated or not
|
|
||||||
Additional keyword arguments are passed on to make_keyframes.
|
Additional keyword arguments are passed on to make_keyframes.
|
||||||
"""
|
"""
|
||||||
progress_set_message("Looking for keyframes")
|
|
||||||
progress_set_indeterminate()
|
|
||||||
|
|
||||||
kffilename = make_keyframes_filename(filename)
|
kffilename = make_keyframes_filename(filename)
|
||||||
|
|
||||||
if not os.path.exists(kffilename):
|
if not os.path.exists(kffilename):
|
||||||
if generate == GenKeyframesMode.NEVER:
|
vs.core.log_message(vs.MESSAGE_TYPE_INFORMATION, "No keyframes file found, detecting keyframes...\n")
|
||||||
return fallback
|
|
||||||
if generate == GenKeyframesMode.ASK and not ask_callback(filename):
|
|
||||||
return fallback
|
|
||||||
|
|
||||||
keyframes = make_keyframes(clip, **kwargs)
|
keyframes = make_keyframes(clip, **kwargs)
|
||||||
save_keyframes(kffilename, keyframes)
|
save_keyframes(kffilename, keyframes)
|
||||||
|
|
||||||
|
@ -308,15 +245,13 @@ def get_keyframes(filename: str, clip: vs.VideoNode, fallback: str | List[int],
|
||||||
def check_audio(filename: str, **kwargs: Any) -> bool:
|
def check_audio(filename: str, **kwargs: Any) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks whether the given file has an audio track by trying to open it with
|
Checks whether the given file has an audio track by trying to open it with
|
||||||
BestSource. Requires the `bs` plugin to return correct results, but
|
BestAudioSource. Requires the `bas` plugin to return correct results, but
|
||||||
won't crash if it's not installed.
|
won't crash if it's not installed.
|
||||||
Additional keyword arguments are passed on to BestSource.
|
Additional keyword arguments are passed on to BestAudioSource.
|
||||||
"""
|
"""
|
||||||
progress_set_message("Checking if the file has an audio track")
|
|
||||||
progress_set_indeterminate()
|
|
||||||
try:
|
try:
|
||||||
ensure_plugin("bs", "BestSource", "")
|
ensure_plugin("bas", "BestAudioSource", "")
|
||||||
vs.core.bs.AudioSource(source=filename, **kwargs)
|
vs.core.bas.Source(source=filename, **kwargs)
|
||||||
return True
|
return True
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
# Copy files to build directory for testing purposes
|
|
||||||
vs_files = files(
|
|
||||||
'aegisub_vs.py',
|
|
||||||
)
|
|
||||||
|
|
||||||
foreach f: vs_files
|
|
||||||
configure_file(input: f, output: '@PLAINNAME@', copy: true)
|
|
||||||
endforeach
|
|
|
@ -23,21 +23,8 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
// classic VSFilter internally uses a signed 32-bit int to denote milliseconds.
|
|
||||||
// To avoid this limit to < 596h (-6 to avoid rounding up to 596h in centisecond precision)
|
|
||||||
static const int MAX_TIME = 596 * 60 * 60 * 1000 - 6;
|
|
||||||
|
|
||||||
static void decompose_time(int ms_time, int& h, int& m, int& s, int& ms) {
|
|
||||||
h = ms_time / 3600000;
|
|
||||||
ms_time -= h * 3600000;
|
|
||||||
m = ms_time / 60000;
|
|
||||||
ms_time -= m * 60000;
|
|
||||||
s = ms_time / 1000;
|
|
||||||
ms = ms_time - s * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace agi {
|
namespace agi {
|
||||||
Time::Time(int time) : time(util::mid(0, time, MAX_TIME)) { }
|
Time::Time(int time) : time(util::mid(0, time, 10 * 60 * 60 * 1000 - 6)) { }
|
||||||
|
|
||||||
Time::Time(std::string const& text) {
|
Time::Time(std::string const& text) {
|
||||||
int after_decimal = -1;
|
int after_decimal = -1;
|
||||||
|
@ -69,25 +56,38 @@ Time::Time(std::string const& text) {
|
||||||
time = (time * 60 + current) * 1000;
|
time = (time * 60 + current) * 1000;
|
||||||
|
|
||||||
// Limit to the valid range
|
// Limit to the valid range
|
||||||
time = util::mid(0, time, MAX_TIME);
|
time = util::mid(0, time, 10 * 60 * 60 * 1000 - 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Time::GetAssFormatted(bool msPrecision) const {
|
std::string Time::GetAssFormatted(bool msPrecision) const {
|
||||||
int ass_time = msPrecision ? time : int(*this);
|
int ass_time = msPrecision ? time : int(*this);
|
||||||
int h, m, s, ms;
|
std::string ret(10 + msPrecision, ':');
|
||||||
|
ret[0] = '0' + ass_time / 3600000;
|
||||||
decompose_time(ass_time, h, m, s, ms);
|
ret[2] = '0' + (ass_time % (60 * 60 * 1000)) / (60 * 1000 * 10);
|
||||||
|
ret[3] = '0' + (ass_time % (10 * 60 * 1000)) / (60 * 1000);
|
||||||
if (!msPrecision)
|
ret[5] = '0' + (ass_time % (60 * 1000)) / (1000 * 10);
|
||||||
return format("%d:%02d:%02d.%02d", h, m, s, ms / 10);
|
ret[6] = '0' + (ass_time % (10 * 1000)) / 1000;
|
||||||
else
|
ret[7] = '.';
|
||||||
return format("%d:%02d:%02d.%03d", h, m, s, ms);
|
ret[8] = '0' + (ass_time % 1000) / 100;
|
||||||
|
ret[9] = '0' + (ass_time % 100) / 10;
|
||||||
|
if (msPrecision)
|
||||||
|
ret[10] = '0' + ass_time % 10;
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Time::GetSrtFormatted() const {
|
std::string Time::GetSrtFormatted() const {
|
||||||
int h, m, s, ms;
|
std::string ret(12, ':');
|
||||||
decompose_time(time, h, m, s, ms);
|
ret[0] = '0';
|
||||||
return format("%02d:%02d:%02d,%03d", h, m, s, ms);
|
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)
|
SmpteFormatter::SmpteFormatter(vfr::Framerate fps, char sep)
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <boost/range/algorithm.hpp>
|
#include <boost/range/algorithm.hpp>
|
||||||
|
|
||||||
#include <libaegisub/charset_conv.h>
|
#include <libaegisub/charset_conv.h>
|
||||||
|
#include <iconv.h>
|
||||||
|
|
||||||
#include "charset_6937.h"
|
#include "charset_6937.h"
|
||||||
|
|
||||||
|
@ -419,7 +420,7 @@ size_t IconvWrapper::DstStrLen(const char* str) {
|
||||||
bool IsConversionSupported(const char *src, const char *dst) {
|
bool IsConversionSupported(const char *src, const char *dst) {
|
||||||
iconv_t cd = iconv_open(dst, src);
|
iconv_t cd = iconv_open(dst, src);
|
||||||
bool supported = cd != iconv_invalid;
|
bool supported = cd != iconv_invalid;
|
||||||
if (supported) iconv_close(cd);
|
iconv_close(cd);
|
||||||
return supported;
|
return supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,15 +19,21 @@
|
||||||
#include "libaegisub/color.h"
|
#include "libaegisub/color.h"
|
||||||
#include "libaegisub/ass/dialogue_parser.h"
|
#include "libaegisub/ass/dialogue_parser.h"
|
||||||
|
|
||||||
#include <boost/phoenix/core.hpp>
|
|
||||||
#include <boost/phoenix/operator.hpp>
|
|
||||||
#include <boost/phoenix/fusion.hpp>
|
|
||||||
#include <boost/phoenix/statement.hpp>
|
|
||||||
|
|
||||||
#include <boost/spirit/include/qi.hpp>
|
#include <boost/spirit/include/qi.hpp>
|
||||||
|
#include <boost/spirit/include/phoenix_core.hpp>
|
||||||
|
#include <boost/spirit/include/phoenix_operator.hpp>
|
||||||
|
#include <boost/spirit/include/phoenix_fusion.hpp>
|
||||||
#include <boost/fusion/include/adapt_struct.hpp>
|
#include <boost/fusion/include/adapt_struct.hpp>
|
||||||
#include <boost/spirit/include/lex_lexertl.hpp>
|
#include <boost/spirit/include/lex_lexertl.hpp>
|
||||||
|
|
||||||
|
// We have to use the copy of pheonix within spirit if it exists, as the
|
||||||
|
// standalone copy has different header guards
|
||||||
|
#ifdef HAVE_BOOST_SPIRIT_HOME_PHOENIX_VERSION_HPP
|
||||||
|
#include <boost/spirit/home/phoenix/statement.hpp>
|
||||||
|
#else
|
||||||
|
#include <boost/phoenix/statement.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
BOOST_FUSION_ADAPT_STRUCT(
|
BOOST_FUSION_ADAPT_STRUCT(
|
||||||
agi::Color,
|
agi::Color,
|
||||||
(unsigned char, r)
|
(unsigned char, r)
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <libaegisub/exception.h>
|
#include <libaegisub/exception.h>
|
||||||
#include <iconv.h>
|
|
||||||
|
|
||||||
namespace agi {
|
namespace agi {
|
||||||
namespace charset {
|
namespace charset {
|
||||||
|
@ -35,6 +34,8 @@ DEFINE_EXCEPTION(BufferTooSmall, ConversionFailure);
|
||||||
DEFINE_EXCEPTION(BadInput, ConversionFailure);
|
DEFINE_EXCEPTION(BadInput, ConversionFailure);
|
||||||
DEFINE_EXCEPTION(BadOutput, ConversionFailure);
|
DEFINE_EXCEPTION(BadOutput, ConversionFailure);
|
||||||
|
|
||||||
|
typedef void *iconv_t;
|
||||||
|
|
||||||
/// RAII handle for iconv
|
/// RAII handle for iconv
|
||||||
class Iconv {
|
class Iconv {
|
||||||
iconv_t cd;
|
iconv_t cd;
|
||||||
|
|
|
@ -24,8 +24,6 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#undef CreateDirectory
|
#undef CreateDirectory
|
||||||
|
|
||||||
namespace agi {
|
namespace agi {
|
||||||
|
|
|
@ -129,6 +129,7 @@ struct LuaStackcheck {
|
||||||
void dump();
|
void dump();
|
||||||
|
|
||||||
LuaStackcheck(lua_State *L) : L(L), startstack(lua_gettop(L)) { }
|
LuaStackcheck(lua_State *L) : L(L), startstack(lua_gettop(L)) { }
|
||||||
|
~LuaStackcheck() { check_stack(0); }
|
||||||
};
|
};
|
||||||
#else
|
#else
|
||||||
struct LuaStackcheck {
|
struct LuaStackcheck {
|
||||||
|
|
|
@ -84,12 +84,6 @@ public:
|
||||||
Color const& GetColor() const;
|
Color const& GetColor() const;
|
||||||
bool const& GetBool() const;
|
bool const& GetBool() const;
|
||||||
|
|
||||||
std::string const& GetDefaultString() const;
|
|
||||||
int64_t const& GetDefaultInt() const;
|
|
||||||
double const& GetDefaultDouble() const;
|
|
||||||
Color const& GetDefaultColor() const;
|
|
||||||
bool const& GetDefaultBool() const;
|
|
||||||
|
|
||||||
void SetString(const std::string);
|
void SetString(const std::string);
|
||||||
void SetInt(const int64_t);
|
void SetInt(const int64_t);
|
||||||
void SetDouble(const double);
|
void SetDouble(const double);
|
||||||
|
@ -102,12 +96,6 @@ public:
|
||||||
std::vector<Color> const& GetListColor() const;
|
std::vector<Color> const& GetListColor() const;
|
||||||
std::vector<bool> const& GetListBool() const;
|
std::vector<bool> const& GetListBool() const;
|
||||||
|
|
||||||
std::vector<std::string> const& GetDefaultListString() const;
|
|
||||||
std::vector<int64_t> const& GetDefaultListInt() const;
|
|
||||||
std::vector<double> const& GetDefaultListDouble() const;
|
|
||||||
std::vector<Color> const& GetDefaultListColor() const;
|
|
||||||
std::vector<bool> const& GetDefaultListBool() const;
|
|
||||||
|
|
||||||
void SetListString(std::vector<std::string>);
|
void SetListString(std::vector<std::string>);
|
||||||
void SetListInt(std::vector<int64_t>);
|
void SetListInt(std::vector<int64_t>);
|
||||||
void SetListDouble(std::vector<double>);
|
void SetListDouble(std::vector<double>);
|
||||||
|
@ -129,7 +117,6 @@ public:
|
||||||
: OptionValue(std::move(member_name)) \
|
: OptionValue(std::move(member_name)) \
|
||||||
, value(member_value), value_default(member_value) { } \
|
, value(member_value), value_default(member_value) { } \
|
||||||
type const& GetValue() const { return value; } \
|
type const& GetValue() const { return value; } \
|
||||||
type const& GetDefault() const { return value_default; } \
|
|
||||||
void SetValue(type new_val) { value = std::move(new_val); NotifyChanged(); } \
|
void SetValue(type new_val) { value = std::move(new_val); NotifyChanged(); } \
|
||||||
OptionType GetType() const { return OptionType::type_name; } \
|
OptionType GetType() const { return OptionType::type_name; } \
|
||||||
void Reset() { value = value_default; NotifyChanged(); } \
|
void Reset() { value = value_default; NotifyChanged(); } \
|
||||||
|
@ -154,7 +141,6 @@ CONFIG_OPTIONVALUE(Bool, bool)
|
||||||
: OptionValue(std::move(name)) \
|
: OptionValue(std::move(name)) \
|
||||||
, array(value), array_default(value) { } \
|
, array(value), array_default(value) { } \
|
||||||
std::vector<type> const& GetValue() const { return array; } \
|
std::vector<type> const& GetValue() const { return array; } \
|
||||||
std::vector<type> const& GetDefault() const { return array_default; } \
|
|
||||||
void SetValue(std::vector<type> val) { array = std::move(val); NotifyChanged(); } \
|
void SetValue(std::vector<type> val) { array = std::move(val); NotifyChanged(); } \
|
||||||
OptionType GetType() const { return OptionType::List##type_name; } \
|
OptionType GetType() const { return OptionType::List##type_name; } \
|
||||||
void Reset() { array = array_default; NotifyChanged(); } \
|
void Reset() { array = array_default; NotifyChanged(); } \
|
||||||
|
@ -170,7 +156,6 @@ CONFIG_OPTIONVALUE_LIST(Bool, bool)
|
||||||
|
|
||||||
#define CONFIG_OPTIONVALUE_ACCESSORS(ReturnType, Type) \
|
#define CONFIG_OPTIONVALUE_ACCESSORS(ReturnType, Type) \
|
||||||
inline ReturnType const& OptionValue::Get##Type() const { return As<OptionValue##Type>(OptionType::Type)->GetValue(); } \
|
inline ReturnType const& OptionValue::Get##Type() const { return As<OptionValue##Type>(OptionType::Type)->GetValue(); } \
|
||||||
inline ReturnType const& OptionValue::GetDefault##Type() const { return As<OptionValue##Type>(OptionType::Type)->GetDefault(); } \
|
|
||||||
inline void OptionValue::Set##Type(ReturnType v) { As<OptionValue##Type>(OptionType::Type)->SetValue(std::move(v)); }
|
inline void OptionValue::Set##Type(ReturnType v) { As<OptionValue##Type>(OptionType::Type)->SetValue(std::move(v)); }
|
||||||
|
|
||||||
CONFIG_OPTIONVALUE_ACCESSORS(std::string, String)
|
CONFIG_OPTIONVALUE_ACCESSORS(std::string, String)
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
#include "fs_fwd.h"
|
#include "fs_fwd.h"
|
||||||
|
|
||||||
#include <map>
|
#include <boost/container/flat_map.hpp>
|
||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -27,7 +27,7 @@ namespace charset { class IconvWrapper; }
|
||||||
|
|
||||||
class Thesaurus {
|
class Thesaurus {
|
||||||
/// Map of word -> byte position in the data file
|
/// Map of word -> byte position in the data file
|
||||||
std::map<std::string, size_t> offsets;
|
boost::container::flat_map<std::string, size_t> offsets;
|
||||||
/// Read handle to the data file
|
/// Read handle to the data file
|
||||||
std::unique_ptr<read_file_mapping> dat;
|
std::unique_ptr<read_file_mapping> dat;
|
||||||
/// Converter from the data file's charset to UTF-8
|
/// Converter from the data file's charset to UTF-8
|
||||||
|
|
|
@ -75,13 +75,14 @@ void Path::FillPlatformSpecificPaths() {
|
||||||
SetToken("?local", home/".aegisub");
|
SetToken("?local", home/".aegisub");
|
||||||
|
|
||||||
#ifdef APPIMAGE_BUILD
|
#ifdef APPIMAGE_BUILD
|
||||||
agi::fs::path exe = exe_dir();
|
agi::fs::path data = exe_dir();
|
||||||
agi::fs::path data_from_bin = agi::fs::path(P_DATA).lexically_relative(P_BIN);
|
if (data == "") data = home/".aegisub";
|
||||||
SetToken("?data", (exe != "" ? exe/data_from_bin : home/".aegisub").make_preferred());
|
SetToken("?data", data);
|
||||||
|
SetToken("?dictionary", Decode("?data/dictionaries"));
|
||||||
#else
|
#else
|
||||||
SetToken("?data", P_DATA);
|
SetToken("?data", P_DATA);
|
||||||
#endif
|
|
||||||
SetToken("?dictionary", "/usr/share/hunspell");
|
SetToken("?dictionary", "/usr/share/hunspell");
|
||||||
|
#endif
|
||||||
|
|
||||||
#else
|
#else
|
||||||
agi::fs::path app_support = agi::util::GetApplicationSupportDirectory();
|
agi::fs::path app_support = agi::util::GetApplicationSupportDirectory();
|
||||||
|
|
42
meson.build
42
meson.build
|
@ -1,14 +1,13 @@
|
||||||
project('Aegisub', ['c', 'cpp'],
|
project('Aegisub', ['c', 'cpp'],
|
||||||
license: 'BSD-3-Clause',
|
license: 'BSD-3-Clause',
|
||||||
meson_version: '>=1.0.0',
|
meson_version: '>=0.57.0',
|
||||||
default_options: ['cpp_std=c++17', 'buildtype=debugoptimized'],
|
default_options: ['cpp_std=c++14', 'buildtype=debugoptimized'],
|
||||||
version: '3.2.2')
|
version: '3.2.2')
|
||||||
|
|
||||||
cmake = import('cmake')
|
cmake = import('cmake')
|
||||||
|
|
||||||
if host_machine.system() == 'windows'
|
if host_machine.system() == 'windows'
|
||||||
add_project_arguments('-DNOMINMAX', language: 'cpp')
|
add_project_arguments('-DNOMINMAX', language: 'cpp')
|
||||||
add_project_arguments('-DUNICODE', language: 'cpp')
|
|
||||||
|
|
||||||
if not get_option('csri').disabled()
|
if not get_option('csri').disabled()
|
||||||
add_global_arguments('-DCSRI_NO_EXPORT', language: 'c')
|
add_global_arguments('-DCSRI_NO_EXPORT', language: 'c')
|
||||||
|
@ -58,15 +57,11 @@ if get_option('buildtype') == 'release'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
conf = configuration_data()
|
conf = configuration_data()
|
||||||
conf.set_quoted('P_BIN', bindir)
|
|
||||||
conf.set_quoted('P_DATA', dataroot)
|
conf.set_quoted('P_DATA', dataroot)
|
||||||
conf.set_quoted('P_LOCALE', localedir)
|
conf.set_quoted('P_LOCALE', localedir)
|
||||||
if get_option('credit') != ''
|
if get_option('credit') != ''
|
||||||
conf.set_quoted('BUILD_CREDIT', get_option('credit'))
|
conf.set_quoted('BUILD_CREDIT', get_option('credit'))
|
||||||
endif
|
endif
|
||||||
if get_option('build_appimage')
|
|
||||||
conf.set('APPIMAGE_BUILD', 1)
|
|
||||||
endif
|
|
||||||
conf.set('WITH_UPDATE_CHECKER', get_option('enable_update_checker'))
|
conf.set('WITH_UPDATE_CHECKER', get_option('enable_update_checker'))
|
||||||
|
|
||||||
deps = []
|
deps = []
|
||||||
|
@ -103,7 +98,7 @@ deps += dependency('libass', version: '>=0.9.7',
|
||||||
|
|
||||||
boost_modules = ['chrono', 'filesystem', 'thread', 'locale', 'regex']
|
boost_modules = ['chrono', 'filesystem', 'thread', 'locale', 'regex']
|
||||||
if not get_option('local_boost')
|
if not get_option('local_boost')
|
||||||
boost_dep = dependency('boost', version: '>=1.70.0',
|
boost_dep = dependency('boost', version: '>=1.50.0',
|
||||||
modules: boost_modules + ['system'],
|
modules: boost_modules + ['system'],
|
||||||
required: false,
|
required: false,
|
||||||
static: get_option('default_library') == 'static')
|
static: get_option('default_library') == 'static')
|
||||||
|
@ -145,24 +140,18 @@ else
|
||||||
endif
|
endif
|
||||||
|
|
||||||
opt_var = cmake.subproject_options()
|
opt_var = cmake.subproject_options()
|
||||||
opt_var.set_override_option('cpp_std', 'c++14')
|
|
||||||
opt_var.add_cmake_defines({
|
opt_var.add_cmake_defines({
|
||||||
'wxBUILD_INSTALL': false,
|
'wxBUILD_INSTALL': false,
|
||||||
'wxBUILD_PRECOMP': 'OFF', # otherwise breaks project generation w/ meson
|
'wxBUILD_PRECOMP': false, # otherwise breaks project generation w/ meson
|
||||||
'wxBUILD_SHARED': build_shared,
|
'wxBUILD_SHARED': build_shared,
|
||||||
|
|
||||||
'wxUSE_WEBVIEW': false, # breaks build on linux
|
'wxUSE_WEBVIEW': false, # breaks build on linux
|
||||||
'wxUSE_REGEX': 'builtin',
|
|
||||||
'CMAKE_BUILD_TYPE': build_type,
|
'CMAKE_BUILD_TYPE': build_type,
|
||||||
'wxUSE_IMAGE': true,
|
'wxUSE_IMAGE': true,
|
||||||
'wxBUILD_MONOLITHIC': true # otherwise breaks project generation w/ meson
|
'wxBUILD_MONOLITHIC': true # otherwise breaks project generation w/ meson
|
||||||
})
|
})
|
||||||
|
|
||||||
if get_option('wx_version').version_compare('>=3.3.0')
|
wx = cmake.subproject('wxWidgets', options: opt_var)
|
||||||
wx = cmake.subproject('wxWidgets-master', options: opt_var)
|
|
||||||
else
|
|
||||||
wx = cmake.subproject('wxWidgets', options: opt_var)
|
|
||||||
endif
|
|
||||||
|
|
||||||
deps += [
|
deps += [
|
||||||
wx.dependency('wxmono'),
|
wx.dependency('wxmono'),
|
||||||
|
@ -170,12 +159,6 @@ else
|
||||||
wx.dependency('wxscintilla')
|
wx.dependency('wxscintilla')
|
||||||
]
|
]
|
||||||
|
|
||||||
if get_option('wx_version').version_compare('>=3.3.0')
|
|
||||||
deps += [
|
|
||||||
wx.dependency('wxlexilla')
|
|
||||||
]
|
|
||||||
endif
|
|
||||||
|
|
||||||
if host_machine.system() == 'windows' or host_machine.system() == 'darwin'
|
if host_machine.system() == 'windows' or host_machine.system() == 'darwin'
|
||||||
deps += [
|
deps += [
|
||||||
wx.dependency('wxpng'),
|
wx.dependency('wxpng'),
|
||||||
|
@ -249,7 +232,7 @@ needs_ffmpeg = false
|
||||||
|
|
||||||
if get_option('bestsource').enabled()
|
if get_option('bestsource').enabled()
|
||||||
conf.set('WITH_BESTSOURCE', 1)
|
conf.set('WITH_BESTSOURCE', 1)
|
||||||
bs = subproject('bestsource', default_options: ['enable_plugin=false'])
|
bs = subproject('bestsource')
|
||||||
deps += bs.get_variable('bestsource_dep')
|
deps += bs.get_variable('bestsource_dep')
|
||||||
dep_avail += 'BestSource'
|
dep_avail += 'BestSource'
|
||||||
needs_ffmpeg = true
|
needs_ffmpeg = true
|
||||||
|
@ -328,12 +311,8 @@ if host_machine.system() == 'windows'
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if host_machine.system() == 'windows' and cc.has_header('dwrite_3.h')
|
|
||||||
conf.set('HAVE_DWRITE_3', 1)
|
|
||||||
endif
|
|
||||||
|
|
||||||
if host_machine.system() == 'darwin'
|
if host_machine.system() == 'darwin'
|
||||||
frameworks_dep = dependency('appleframeworks', modules : ['CoreText', 'CoreFoundation', 'AppKit', 'Carbon', 'IOKit', 'QuartzCore'])
|
frameworks_dep = dependency('appleframeworks', modules : ['CoreText', 'CoreFoundation', 'AppKit', 'Carbon', 'IOKit'])
|
||||||
deps += frameworks_dep
|
deps += frameworks_dep
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@ -423,10 +402,7 @@ subdir('libaegisub')
|
||||||
subdir('packages')
|
subdir('packages')
|
||||||
subdir('po')
|
subdir('po')
|
||||||
subdir('src')
|
subdir('src')
|
||||||
|
subdir('tests')
|
||||||
if not meson.is_cross_build()
|
|
||||||
subdir('tests')
|
|
||||||
endif
|
|
||||||
|
|
||||||
aegisub_cpp_pch = ['src/include/agi_pre.h']
|
aegisub_cpp_pch = ['src/include/agi_pre.h']
|
||||||
aegisub_c_pch = ['src/include/agi_pre_c.h']
|
aegisub_c_pch = ['src/include/agi_pre_c.h']
|
||||||
|
@ -439,7 +415,7 @@ if host_machine.system() == 'windows'
|
||||||
link_depends += manifest_file
|
link_depends += manifest_file
|
||||||
endif
|
endif
|
||||||
|
|
||||||
aegisub = executable('aegisub', aegisub_src, version_h, acconf, resrc,
|
aegisub = executable('aegisub', aegisub_src, version_h, acconf,
|
||||||
link_with: [libresrc, libluabins, libaegisub],
|
link_with: [libresrc, libluabins, libaegisub],
|
||||||
link_args: link_args,
|
link_args: link_args,
|
||||||
link_depends: link_depends,
|
link_depends: link_depends,
|
||||||
|
|
|
@ -28,4 +28,3 @@ option('update_server', type: 'string', value: 'updates.aegisub.org', descriptio
|
||||||
option('update_url', type: 'string', value: '/trunk', description: 'Base path to use for the update checker')
|
option('update_url', type: 'string', value: '/trunk', description: 'Base path to use for the update checker')
|
||||||
|
|
||||||
option('build_osx_bundle', type: 'boolean', value: 'false', description: 'Package Aegisub.app on OSX')
|
option('build_osx_bundle', type: 'boolean', value: 'false', description: 'Package Aegisub.app on OSX')
|
||||||
option('build_appimage', type: 'boolean', value: 'false', description: 'Prepare for AppImage packaging')
|
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
<?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>
|
|
||||||
<!-- XXX: appstreamcli validation warning: cid-desktopapp-is-not-rdns
|
|
||||||
If improving this, the <id> and filename should probably also be changed. -->
|
|
||||||
<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>https://aegisub.org/img/screenshots/unix/typesetting.png</image>
|
|
||||||
</screenshot>
|
|
||||||
<screenshot>
|
|
||||||
<caption>Audio video</caption>
|
|
||||||
<image>https://aegisub.org/img/screenshots/unix/audio-video.png</image>
|
|
||||||
</screenshot>
|
|
||||||
<screenshot>
|
|
||||||
<caption>Audio timing</caption>
|
|
||||||
<image>https://aegisub.org/img/screenshots/unix/audio-timing.png</image>
|
|
||||||
</screenshot>
|
|
||||||
</screenshots>
|
|
||||||
<developer_name>Aegisub Group</developer_name>
|
|
||||||
<url type="bugtracker">https://github.com/Aegisub/Aegisub/issues</url>
|
|
||||||
<url type="faq">https://aegisub.org/docs/latest/faq</url>
|
|
||||||
<url type="help">https://aegisub.org/docs/latest</url>
|
|
||||||
<url type="homepage">https://aegisub.org</url>
|
|
||||||
<url type="translate">https://github.com/Aegisub/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>
|
|
||||||
<!-- TODO: automatic replace at config time -->
|
|
||||||
<release version="3.2.2" date="2014-12-08"/>
|
|
||||||
</releases>
|
|
||||||
</component>
|
|
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>
|
|
@ -9,7 +9,7 @@ TryExec=@AEGISUB_COMMAND@
|
||||||
Icon=aegisub
|
Icon=aegisub
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=AudioVideo;AudioVideoEditing;GTK;
|
Categories=AudioVideo;AudioVideoEditing;GTK;
|
||||||
Keywords=subtitles;subtitle;captions;captioning;video;audio;
|
_Keywords=subtitles;subtitle;captions;captioning;video;audio;
|
||||||
MimeType=application/x-srt;text/plain;text/x-ass;text/x-microdvd;text/x-ssa;
|
MimeType=application/x-srt;text/plain;text/x-ass;text/x-microdvd;text/x-ssa;
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
StartupWMClass=aegisub
|
StartupWMClass=aegisub
|
|
@ -18,8 +18,8 @@ elif host_machine.system() == 'darwin'
|
||||||
else
|
else
|
||||||
conf_pkg.set('AEGISUB_COMMAND', 'aegisub')
|
conf_pkg.set('AEGISUB_COMMAND', 'aegisub')
|
||||||
|
|
||||||
desktop_template = configure_file(input: 'desktop/aegisub.desktop.in.in',
|
desktop_template = configure_file(input: 'desktop/aegisub.desktop.template.in',
|
||||||
output: 'aegisub.desktop.in',
|
output: 'aegisub.desktop.template',
|
||||||
configuration: conf_pkg)
|
configuration: conf_pkg)
|
||||||
|
|
||||||
i18n = import('i18n')
|
i18n = import('i18n')
|
||||||
|
@ -30,16 +30,6 @@ else
|
||||||
install: true,
|
install: true,
|
||||||
install_dir: datadir / 'applications')
|
install_dir: datadir / 'applications')
|
||||||
|
|
||||||
appdata_template = configure_file(input: 'desktop/aegisub.appdata.xml.in.in',
|
|
||||||
output: 'aegisub.desktop.appdata.xml.in',
|
|
||||||
configuration: conf_pkg)
|
|
||||||
i18n.merge_file(input: appdata_template,
|
|
||||||
output: 'aegisub.appdata.xml',
|
|
||||||
type: 'xml',
|
|
||||||
po_dir: '../po',
|
|
||||||
install: true,
|
|
||||||
install_dir: datadir / 'metainfo')
|
|
||||||
|
|
||||||
aegisub_logos = ['16x16.png', '22x22.png', '24x24.png', '32x32.png', '48x48.png', '64x64.png', 'scalable.svg']
|
aegisub_logos = ['16x16.png', '22x22.png', '24x24.png', '32x32.png', '48x48.png', '64x64.png', 'scalable.svg']
|
||||||
|
|
||||||
foreach s: aegisub_logos
|
foreach s: aegisub_logos
|
||||||
|
@ -48,10 +38,4 @@ else
|
||||||
install_data('desktop' / dir / 'aegisub.' + ext,
|
install_data('desktop' / dir / 'aegisub.' + ext,
|
||||||
install_dir: datadir / 'icons' / 'hicolor' / dir / 'apps')
|
install_dir: datadir / 'icons' / 'hicolor' / dir / 'apps')
|
||||||
endforeach
|
endforeach
|
||||||
|
|
||||||
if get_option('build_appimage')
|
|
||||||
install_symlink('AppRun', install_dir: '/', pointing_to: bindir.strip('/') / 'aegisub')
|
|
||||||
install_symlink('.DirIcon', install_dir: '/', pointing_to: datadir.strip('/') / 'icons' / 'hicolor' / 'scalable' / 'apps' / 'aegisub.svg')
|
|
||||||
install_symlink('aegisub.desktop', install_dir: '/', pointing_to: datadir.strip('/') / 'applications' / 'aegisub.desktop')
|
|
||||||
endif
|
|
||||||
endif
|
endif
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
[Files]
|
[Files]
|
||||||
; Avisynth
|
; Avisynth
|
||||||
DestDir: {app}; Source: {#DEPS_DIR}\AvisynthPlus64\x64\Output\system\DevIL.dll; Flags: ignoreversion; Components: main
|
DestDir: {app}; Source: {#DEPS_DIR}\AvisynthPlus64\x64\system\DevIL.dll; Flags: ignoreversion; Components: main
|
||||||
DestDir: {app}; Source: {#DEPS_DIR}\AvisynthPlus64\x64\Output\AviSynth.dll; Flags: ignoreversion; Components: main
|
DestDir: {app}; Source: {#DEPS_DIR}\AvisynthPlus64\x64\AviSynth.dll; Flags: ignoreversion; Components: main
|
||||||
DestDir: {app}; Source: {#DEPS_DIR}\AvisynthPlus64\x64\Output\plugins\DirectShowSource.dll; Flags: ignoreversion; Components: main
|
DestDir: {app}; Source: {#DEPS_DIR}\AvisynthPlus64\x64\plugins\DirectShowSource.dll; Flags: ignoreversion; Components: main
|
||||||
; VSFilter
|
; VSFilter
|
||||||
DestDir: {app}\csri; Source: {#DEPS_DIR}\VSFilter\x64\VSFilter.dll; Flags: ignoreversion; Components: main
|
DestDir: {app}\csri; Source: {#DEPS_DIR}\VSFilter\x64\VSFilter.dll; Flags: ignoreversion; Components: main
|
||||||
; VapourSynth
|
; VapourSynth
|
||||||
DestDir: {app}\vapoursynth; Source: {#DEPS_DIR}\L-SMASH-Works\libvslsmashsource.dll; Flags: ignoreversion; Components: vapoursynth
|
DestDir: {app}\vapoursynth; Source: {#DEPS_DIR}\L-SMASH-Works\libvslsmashsource.dll; Flags: ignoreversion; Components: vapoursynth
|
||||||
DestDir: {app}\vapoursynth; Source: {#DEPS_DIR}\BestSource\BestSource.dll; Flags: ignoreversion; Components: vapoursynth
|
DestDir: {app}\vapoursynth; Source: {#DEPS_DIR}\bestaudiosource\win64\BestAudioSource.dll; Flags: ignoreversion; Components: vapoursynth
|
||||||
DestDir: {app}\vapoursynth; Source: {#DEPS_DIR}\SCXVid\libscxvid.dll; Flags: ignoreversion; Components: vapoursynth
|
DestDir: {app}\vapoursynth; Source: {#DEPS_DIR}\SCXVid\libscxvid.dll; Flags: ignoreversion; Components: vapoursynth
|
||||||
DestDir: {app}\vapoursynth; Source: {#DEPS_DIR}\WWXD\libwwxd64.dll; Flags: ignoreversion; Components: vapoursynth
|
DestDir: {app}\vapoursynth; Source: {#DEPS_DIR}\WWXD\libwwxd64.dll; Flags: ignoreversion; Components: vapoursynth
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
DestDir: {tmp}; Source: "{#DEPS_DIR}\VC_redist\VC_redist.x{#ARCH}.exe"; Flags: nocompression deleteafterinstall
|
DestDir: {tmp}; Source: "{#DEPS_DIR}\VC_redist\VC_redist.x{#ARCH}.exe"; Flags: nocompression deleteafterinstall
|
||||||
DestDir: {app}; Source: "{#DEPS_DIR}\XAudio2_redist\build\native\release\bin\x{#ARCH}\xaudio2_9redist.dll"; DestName: "XAudio2_9.dll"; OnlyBelowVersion: 10.0
|
|
||||||
|
|
||||||
[Run]
|
[Run]
|
||||||
Filename: {tmp}\VC_redist.x{#ARCH}.exe; StatusMsg: {cm:InstallRuntime}; Parameters: "/install /quiet /norestart"
|
Filename: {tmp}\VC_redist.x{#ARCH}.exe; StatusMsg: {cm:InstallRuntime}; Parameters: "/install /quiet /norestart"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
; This file declares all installables related to spell checking and thesaurii in Aegisub
|
; This file declares all installables related to spell checking and thesaurii in Aegisub
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
Source: {#DEPS_DIR}\dictionaries\en_US.aff; DestDir: {app}\dictionaries; Flags: skipifsourcedoesntexist ignoreversion; Components: dictionaries/en_US
|
Source: {#DEPS_DIR}\dictionaries\en_US.aff; DestDir: {app}\dictionaries; Flags: ignoreversion; Components: dictionaries/en_US
|
||||||
Source: {#DEPS_DIR}\dictionaries\en_US.dic; DestDir: {app}\dictionaries; Flags: skipifsourcedoesntexist ignoreversion; Components: dictionaries/en_US
|
Source: {#DEPS_DIR}\dictionaries\en_US.dic; DestDir: {app}\dictionaries; Flags: ignoreversion; Components: dictionaries/en_US
|
||||||
|
|
|
@ -56,9 +56,9 @@ Copy-New-Item $InstallerDepsDir\dictionaries\en_US.aff $PortableOutputDir\dicti
|
||||||
Copy-New-Item $InstallerDepsDir\dictionaries\en_US.dic $PortableOutputDir\dictionaries
|
Copy-New-Item $InstallerDepsDir\dictionaries\en_US.dic $PortableOutputDir\dictionaries
|
||||||
Write-Output 'Copying - codecs'
|
Write-Output 'Copying - codecs'
|
||||||
Write-Output 'Copying - codecs\Avisynth'
|
Write-Output 'Copying - codecs\Avisynth'
|
||||||
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\Output\system\DevIL.dll $PortableOutputDir
|
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\system\DevIL.dll $PortableOutputDir
|
||||||
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\Output\AviSynth.dll $PortableOutputDir
|
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\AviSynth.dll $PortableOutputDir
|
||||||
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\Output\plugins\DirectShowSource.dll $PortableOutputDir
|
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\plugins\DirectShowSource.dll $PortableOutputDir
|
||||||
Write-Output 'Copying - codecs\VapourSynth'
|
Write-Output 'Copying - codecs\VapourSynth'
|
||||||
Copy-New-Item $InstallerDepsDir\L-SMASH-Works\libvslsmashsource.dll $PortableOutputDir\vapoursynth
|
Copy-New-Item $InstallerDepsDir\L-SMASH-Works\libvslsmashsource.dll $PortableOutputDir\vapoursynth
|
||||||
Copy-New-Item $InstallerDepsDir\bestaudiosource\win64\BestAudioSource.dll $PortableOutputDir\vapoursynth
|
Copy-New-Item $InstallerDepsDir\bestaudiosource\win64\BestAudioSource.dll $PortableOutputDir\vapoursynth
|
||||||
|
@ -68,9 +68,6 @@ Write-Output 'Copying - codecs\VSFilter'
|
||||||
Copy-New-Item $InstallerDepsDir\VSFilter\x64\VSFilter.dll $PortableOutputDir\csri
|
Copy-New-Item $InstallerDepsDir\VSFilter\x64\VSFilter.dll $PortableOutputDir\csri
|
||||||
Write-Output 'Copying - runtimes\MS-CRT'
|
Write-Output 'Copying - runtimes\MS-CRT'
|
||||||
Copy-New-Item $InstallerDepsDir\VC_redist\VC_redist.x64.exe $PortableOutputDir\Microsoft.CRT
|
Copy-New-Item $InstallerDepsDir\VC_redist\VC_redist.x64.exe $PortableOutputDir\Microsoft.CRT
|
||||||
Write-Output 'Copying - redist\XAudio2_9'
|
|
||||||
Copy-New-Item $InstallerDepsDir\XAudio2_redist\build\native\release\bin\x64\xaudio2_9redist.dll $PortableOutputDir\Redist
|
|
||||||
Rename-Item $PortableOutputDir\Redist\xaudio2_9redist.dll $PortableOutputDir\Redist\XAudio2_9.dll
|
|
||||||
|
|
||||||
Write-Output 'Copying - automation'
|
Write-Output 'Copying - automation'
|
||||||
Copy-New-Items "$InstallerDir\share\aegisub\automation\*" "$PortableOutputDir\automation\" -Recurse
|
Copy-New-Items "$InstallerDir\share\aegisub\automation\*" "$PortableOutputDir\automation\" -Recurse
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
packages/desktop/aegisub.desktop.in.in
|
|
||||||
packages/desktop/aegisub.appdata.xml.in.in
|
|
||||||
|
|
||||||
src/ass_style.cpp
|
src/ass_style.cpp
|
||||||
src/audio_box.cpp
|
src/audio_box.cpp
|
||||||
src/audio_karaoke.cpp
|
src/audio_karaoke.cpp
|
||||||
|
|
11962
po/aegisub.pot
11962
po/aegisub.pot
File diff suppressed because it is too large
Load diff
11506
po/fr_FR.po
11506
po/fr_FR.po
File diff suppressed because it is too large
Load diff
|
@ -1,22 +1,19 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
|
||||||
|
|
||||||
maybe_append() {
|
maybe_append() {
|
||||||
while read -r msg; do
|
while read -r msg; do
|
||||||
msgfile=$(printf '%s' "$msg" | cut -d'|' -f1)
|
msgfile=$(echo $msg | cut -d'|' -f1)
|
||||||
msgline=$(printf '%s' "$msg" | cut -d'|' -f2)
|
msgline=$(echo $msg | cut -d'|' -f2)
|
||||||
msgid=$(printf '%s' "$msg" | cut -d'|' -f3-)
|
msgid=$(echo $msg | cut -d'|' -f3-)
|
||||||
|
|
||||||
if ! grep -Fq "msgid $msgid" aegisub.pot; then
|
if ! grep -Fq "msgid $msgid" aegisub.pot; then
|
||||||
printf "\n#: %s:%s\nmsgid %s\nmsgstr \"\"\n\n" \
|
echo "\n#: $msgfile:$msgline\nmsgid $msgid\nmsgstr \"\"\n" >> aegisub.pot
|
||||||
"$msgfile" "$msgline" "$msgid" >> aegisub.pot
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
find ../src ../src/command -name '*.cpp' -o -name '*.h' \
|
find ../src ../src/command -name \*.cpp -o -name \*.h \
|
||||||
| xgettext --files-from=- -o - --c++ --sort-by-file \
|
| xgettext --files-from=- -o - --c++ -k_ -kSTR_MENU -kSTR_DISP -kSTR_HELP -kfmt_tl -kfmt_plural:2,3 -kwxT -kwxPLURAL:1,2 \
|
||||||
-k_ -kSTR_MENU -kSTR_DISP -kSTR_HELP -kfmt_tl -kfmt_plural:2,3 -kwxT -kwxPLURAL:1,2 \
|
|
||||||
| sed 's/SOME DESCRIPTIVE TITLE./Aegisub 3.2/' \
|
| sed 's/SOME DESCRIPTIVE TITLE./Aegisub 3.2/' \
|
||||||
| sed 's/YEAR/2005-2014/' \
|
| sed 's/YEAR/2005-2014/' \
|
||||||
| sed "s/THE PACKAGE'S COPYRIGHT HOLDER/Rodrigo Braz Monteiro, Niels Martin Hansen, Thomas Goyne et. al./" \
|
| sed "s/THE PACKAGE'S COPYRIGHT HOLDER/Rodrigo Braz Monteiro, Niels Martin Hansen, Thomas Goyne et. al./" \
|
||||||
|
@ -36,27 +33,28 @@ grep '"[A-Za-z ]\+" : {' -n ../src/libresrc/default_hotkey.json \
|
||||||
| sed 's/^\([0-9]\+:\).*\("[^"]\+"\).*$/default_hotkey.json|\1|\2/' \
|
| sed 's/^\([0-9]\+:\).*\("[^"]\+"\).*$/default_hotkey.json|\1|\2/' \
|
||||||
| maybe_append
|
| maybe_append
|
||||||
|
|
||||||
find ../automation -name '*.lua' \
|
find ../automation -name *.lua \
|
||||||
| LC_ALL=C sort \
|
| xargs grep tr\"[^\"]\*\" -o -n \
|
||||||
| xargs grep 'tr"[^"]*"' -o -n \
|
|
||||||
| sed 's/\(.*\):\([0-9]\+\):tr\(".*"\)/\1|\2|\3/' \
|
| sed 's/\(.*\):\([0-9]\+\):tr\(".*"\)/\1|\2|\3/' \
|
||||||
|
| sed 's/\\/\\\\\\\\/g' \
|
||||||
| maybe_append
|
| maybe_append
|
||||||
|
|
||||||
xgettext ../packages/desktop/aegisub.desktop.in.in \
|
for i in 'Name' 'GenericName' 'Comment' 'Keywords'
|
||||||
--language=Desktop --join-existing --omit-header -o aegisub.pot
|
|
||||||
|
|
||||||
xgettext ../packages/desktop/aegisub.appdata.xml.in.in \
|
|
||||||
--language=AppData --join-existing --omit-header -o aegisub.pot
|
|
||||||
|
|
||||||
grep '^_[A-Za-z0-9]*=.*' ../packages/win_installer/fragment_strings.iss.in | while read line
|
|
||||||
do
|
do
|
||||||
printf '%s\n' "$line" \
|
grep ^_$i -n ../packages/desktop/aegisub.desktop.template.in \
|
||||||
| sed 's/[^=]*=\(.*\)/packages\/win_installer\/fragment_strings.iss|1|"\1"/' \
|
| sed 's/\([0-9]\+\):[^=]\+=\(.*\)$/aegisub.desktop|\1|"\2"/' \
|
||||||
| maybe_append
|
| maybe_append
|
||||||
done
|
done
|
||||||
|
|
||||||
for lang in $(cat LINGUAS) ; do
|
if which xmlstarlet >/dev/null 2>&1 && which jq >/dev/null 2>&1; then
|
||||||
# If using gettext < 0.21, run twice to avoid reversing order of old strings
|
for i in 'name' 'summary' 'p' 'li' 'caption'; do
|
||||||
# ref: https://savannah.gnu.org/bugs/?58778
|
xmlstarlet sel -t -v "//_$i" ../packages/desktop/aegisub.appdata.xml.template.in | jq -R .
|
||||||
msgmerge --update --backup=none --no-fuzzy-matching --sort-by-file "$lang".po aegisub.pot
|
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
|
||||||
|
echo "$line" \
|
||||||
|
| sed 's/[^=]*=\(.*\)/packages\/win_installer\/fragment_strings.iss|1|"\1"/' \
|
||||||
|
| maybe_append
|
||||||
done
|
done
|
||||||
|
|
|
@ -1,14 +1,4 @@
|
||||||
i18n = import('i18n')
|
i18n = import('i18n')
|
||||||
# This is currently busted on OSX
|
# This is currently busted on OSX
|
||||||
# and incomplete on any platform.
|
|
||||||
# It misses translatable strings not directly found in either
|
|
||||||
# C++ source, desktop or appdata file. This affects strings
|
|
||||||
# of the Windows installer (iss), from Lua scripts and JSON files.
|
|
||||||
# Until a solution is found, POT updates should continue to use make_pot.sh.
|
|
||||||
i18n.gettext('aegisub',
|
i18n.gettext('aegisub',
|
||||||
args: [
|
|
||||||
'-k_', '-kSTR_MENU', '-kSTR_DISP', '-kSTR_HELP', '-kwxT',
|
|
||||||
'-kfmt_tl', '-kfmt_plural:2,3', '-kwxPLURAL:1,2',
|
|
||||||
'--sort-by-file'
|
|
||||||
],
|
|
||||||
install_dir: localedir)
|
install_dir: localedir)
|
||||||
|
|
12526
po/pt_BR.po
12526
po/pt_BR.po
File diff suppressed because it is too large
Load diff
13261
po/pt_PT.po
13261
po/pt_PT.po
File diff suppressed because it is too large
Load diff
13263
po/sr_RS.po
13263
po/sr_RS.po
File diff suppressed because it is too large
Load diff
13315
po/sr_RS@latin.po
13315
po/sr_RS@latin.po
File diff suppressed because it is too large
Load diff
11505
po/uk_UA.po
11505
po/uk_UA.po
File diff suppressed because it is too large
Load diff
12412
po/zh_CN.po
12412
po/zh_CN.po
File diff suppressed because it is too large
Load diff
12393
po/zh_TW.po
12393
po/zh_TW.po
File diff suppressed because it is too large
Load diff
|
@ -242,7 +242,7 @@ uint32_t AssFile::AddExtradata(std::string const& key, std::string const& value)
|
||||||
return data.id;
|
return data.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Extradata.push_back(ExtradataEntry{next_extradata_id, 0, key, value});
|
Extradata.push_back(ExtradataEntry{next_extradata_id, key, value});
|
||||||
return next_extradata_id++; // return old value, then post-increment
|
return next_extradata_id++; // return old value, then post-increment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,16 +340,10 @@ void AssFile::CleanExtradata() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ExtradataEntry &e : Extradata) {
|
|
||||||
if (ids_used.count(e.id))
|
|
||||||
e.expiration_counter = 0;
|
|
||||||
else
|
|
||||||
e.expiration_counter++;
|
|
||||||
}
|
|
||||||
if (ids_used.size() != Extradata.size()) {
|
if (ids_used.size() != Extradata.size()) {
|
||||||
// Erase all no-longer-used extradata entries
|
// Erase all no-longer-used extradata entries
|
||||||
Extradata.erase(std::remove_if(begin(Extradata), end(Extradata), [&](ExtradataEntry const& e) {
|
Extradata.erase(std::remove_if(begin(Extradata), end(Extradata), [&](ExtradataEntry const& e) {
|
||||||
return e.expiration_counter >= 10;
|
return !ids_used.count(e.id);
|
||||||
}), end(Extradata));
|
}), end(Extradata));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,11 +50,17 @@ using EntryList = typename boost::intrusive::make_list<T, boost::intrusive::cons
|
||||||
|
|
||||||
struct ExtradataEntry {
|
struct ExtradataEntry {
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
int expiration_counter;
|
|
||||||
std::string key;
|
std::string key;
|
||||||
std::string value;
|
std::string value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Both start and end are inclusive
|
||||||
|
struct LineFold {
|
||||||
|
int start;
|
||||||
|
int end;
|
||||||
|
bool collapsed;
|
||||||
|
};
|
||||||
|
|
||||||
struct AssFileCommit {
|
struct AssFileCommit {
|
||||||
wxString const& message;
|
wxString const& message;
|
||||||
int *commit_id;
|
int *commit_id;
|
||||||
|
|
|
@ -219,7 +219,7 @@ void AssParser::ParseExtradataLine(std::string const &data) {
|
||||||
|
|
||||||
// ensure next_extradata_id is always at least 1 more than the largest existing id
|
// ensure next_extradata_id is always at least 1 more than the largest existing id
|
||||||
target->next_extradata_id = std::max(id+1, target->next_extradata_id);
|
target->next_extradata_id = std::max(id+1, target->next_extradata_id);
|
||||||
target->Extradata.push_back(ExtradataEntry{id, 0, std::move(key), std::move(value)});
|
target->Extradata.push_back(ExtradataEntry{id, std::move(key), std::move(value)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -192,7 +192,6 @@ void AssStyle::UpdateData() {
|
||||||
|
|
||||||
void AssStyle::GetEncodings(wxArrayString &encodingStrings) {
|
void AssStyle::GetEncodings(wxArrayString &encodingStrings) {
|
||||||
encodingStrings.Clear();
|
encodingStrings.Clear();
|
||||||
encodingStrings.Add(wxString("-1 - ") + _("Auto-detect base direction (libass only)"));
|
|
||||||
encodingStrings.Add(wxString("0 - ") + _("ANSI"));
|
encodingStrings.Add(wxString("0 - ") + _("ANSI"));
|
||||||
encodingStrings.Add(wxString("1 - ") + _("Default"));
|
encodingStrings.Add(wxString("1 - ") + _("Default"));
|
||||||
encodingStrings.Add(wxString("2 - ") + _("Symbol"));
|
encodingStrings.Add(wxString("2 - ") + _("Symbol"));
|
||||||
|
|
|
@ -61,7 +61,7 @@ AudioBox::AudioBox(wxWindow *parent, agi::Context *context)
|
||||||
, controller(context->audioController.get())
|
, controller(context->audioController.get())
|
||||||
, context(context)
|
, context(context)
|
||||||
, audio_open_connection(context->audioController->AddAudioPlayerOpenListener(&AudioBox::OnAudioOpen, this))
|
, audio_open_connection(context->audioController->AddAudioPlayerOpenListener(&AudioBox::OnAudioOpen, this))
|
||||||
, panel(new wxPanel(this, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | (OPT_GET("App/Dark Mode")->GetBool() ? wxBORDER_SIMPLE : wxBORDER_RAISED)))
|
, panel(new wxPanel(this, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_RAISED))
|
||||||
, audioDisplay(new AudioDisplay(panel, context->audioController.get(), context))
|
, audioDisplay(new AudioDisplay(panel, context->audioController.get(), context))
|
||||||
, HorizontalZoom(new wxSlider(panel, Audio_Horizontal_Zoom, -OPT_GET("Audio/Zoom/Horizontal")->GetInt(), -50, 30, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_BOTH))
|
, HorizontalZoom(new wxSlider(panel, Audio_Horizontal_Zoom, -OPT_GET("Audio/Zoom/Horizontal")->GetInt(), -50, 30, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_BOTH))
|
||||||
, VerticalZoom(new wxSlider(panel, Audio_Vertical_Zoom, OPT_GET("Audio/Zoom/Vertical")->GetInt(), 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_BOTH|wxSL_INVERSE))
|
, VerticalZoom(new wxSlider(panel, Audio_Vertical_Zoom, OPT_GET("Audio/Zoom/Vertical")->GetInt(), 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_BOTH|wxSL_INVERSE))
|
||||||
|
|
|
@ -22,10 +22,9 @@
|
||||||
#ifdef WITH_BESTSOURCE
|
#ifdef WITH_BESTSOURCE
|
||||||
#include <libaegisub/audio/provider.h>
|
#include <libaegisub/audio/provider.h>
|
||||||
|
|
||||||
#include "bestsource_common.h"
|
|
||||||
|
|
||||||
#include "audiosource.h"
|
#include "audiosource.h"
|
||||||
|
|
||||||
|
#include "bestsource_common.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
|
||||||
|
@ -39,7 +38,7 @@
|
||||||
namespace {
|
namespace {
|
||||||
class BSAudioProvider final : public agi::AudioProvider {
|
class BSAudioProvider final : public agi::AudioProvider {
|
||||||
std::map<std::string, std::string> bsopts;
|
std::map<std::string, std::string> bsopts;
|
||||||
std::unique_ptr<BestAudioSource> bs;
|
BestAudioSource bs;
|
||||||
AudioProperties properties;
|
AudioProperties properties;
|
||||||
|
|
||||||
void FillBuffer(void *Buf, int64_t Start, int64_t Count) const override;
|
void FillBuffer(void *Buf, int64_t Start, int64_t Count) const override;
|
||||||
|
@ -53,49 +52,32 @@ public:
|
||||||
/// @param filename The filename to open
|
/// @param filename The filename to open
|
||||||
BSAudioProvider::BSAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *br) try
|
BSAudioProvider::BSAudioProvider(agi::fs::path const& filename, agi::BackgroundRunner *br) try
|
||||||
: bsopts()
|
: bsopts()
|
||||||
|
, bs(filename.string(), -1, -1, GetBSCacheFile(filename), &bsopts)
|
||||||
{
|
{
|
||||||
provider_bs::CleanBSCache();
|
bs.SetMaxCacheSize(OPT_GET("Provider/Audio/BestSource/Max Cache Size")->GetInt() << 20);
|
||||||
auto track = provider_bs::SelectTrack(filename, true).first;
|
|
||||||
|
|
||||||
if (track == provider_bs::TrackSelection::NoTracks)
|
|
||||||
throw agi::AudioDataNotFound("no audio tracks found");
|
|
||||||
else if (track == provider_bs::TrackSelection::None)
|
|
||||||
throw agi::UserCancelException("audio loading cancelled by user");
|
|
||||||
|
|
||||||
bool cancelled = false;
|
|
||||||
br->Run([&](agi::ProgressSink *ps) {
|
br->Run([&](agi::ProgressSink *ps) {
|
||||||
ps->SetTitle(from_wx(_("Indexing")));
|
ps->SetTitle(from_wx(_("Exacting")));
|
||||||
ps->SetMessage(from_wx(_("Indexing file... This will take a while!")));
|
ps->SetMessage(from_wx(_("Creating cache... This can take a while!")));
|
||||||
try {
|
ps->SetIndeterminate();
|
||||||
bs = agi::make_unique<BestAudioSource>(filename.string(), static_cast<int>(track), -1, false, 0, 1, provider_bs::GetCacheFile(filename), &bsopts, 0, [=](int Track, int64_t Current, int64_t Total) {
|
if (bs.GetExactDuration()) {
|
||||||
ps->SetProgress(Current, Total);
|
LOG_D("bs") << "File cached and has exact samples.";
|
||||||
return !ps->IsCancelled();
|
|
||||||
});
|
|
||||||
} catch (BestSourceException const& err) {
|
|
||||||
if (std::string(err.what()) == "Indexing canceled by user")
|
|
||||||
cancelled = true;
|
|
||||||
else
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (cancelled)
|
BSCleanCache();
|
||||||
throw agi::UserCancelException("audio loading cancelled by user");
|
properties = bs.GetAudioProperties();
|
||||||
|
float_samples = properties.IsFloat;
|
||||||
bs->SetMaxCacheSize(OPT_GET("Provider/Audio/BestSource/Max Cache Size")->GetInt() << 20);
|
bytes_per_sample = properties.BytesPerSample;
|
||||||
properties = bs->GetAudioProperties();
|
|
||||||
float_samples = properties.AF.Float;
|
|
||||||
bytes_per_sample = properties.AF.BytesPerSample;
|
|
||||||
sample_rate = properties.SampleRate;
|
sample_rate = properties.SampleRate;
|
||||||
channels = properties.Channels;
|
channels = properties.Channels;
|
||||||
num_samples = properties.NumSamples;
|
num_samples = properties.NumSamples;
|
||||||
decoded_samples = OPT_GET("Provider/Audio/BestSource/Aegisub Cache")->GetBool() ? 0 : num_samples;
|
decoded_samples = OPT_GET("Provider/Audio/BestSource/Aegisub Cache")->GetBool() ? 0 : num_samples;
|
||||||
}
|
}
|
||||||
catch (BestSourceException const& err) {
|
catch (AudioException const& err) {
|
||||||
throw agi::AudioProviderError("Failed to create BestAudioSource");
|
throw agi::AudioProviderError("Failed to create BestAudioSource");
|
||||||
}
|
}
|
||||||
|
|
||||||
void BSAudioProvider::FillBuffer(void *Buf, int64_t Start, int64_t Count) const {
|
void BSAudioProvider::FillBuffer(void *Buf, int64_t Start, int64_t Count) const {
|
||||||
bs->GetPackedAudio(reinterpret_cast<uint8_t *>(Buf), Start, Count);
|
const_cast<BestAudioSource &>(bs).GetPackedAudio(reinterpret_cast<uint8_t *>(Buf), Start, Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,11 @@
|
||||||
|
|
||||||
#include "audio_provider_factory.h"
|
#include "audio_provider_factory.h"
|
||||||
|
|
||||||
#include "compat.h"
|
|
||||||
#include "factory_manager.h"
|
#include "factory_manager.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#include <libaegisub/audio/provider.h>
|
#include <libaegisub/audio/provider.h>
|
||||||
#include <libaegisub/format.h>
|
|
||||||
#include <libaegisub/fs.h>
|
#include <libaegisub/fs.h>
|
||||||
#include <libaegisub/log.h>
|
#include <libaegisub/log.h>
|
||||||
#include <libaegisub/path.h>
|
#include <libaegisub/path.h>
|
||||||
|
@ -41,7 +39,6 @@ struct factory {
|
||||||
const char *name;
|
const char *name;
|
||||||
std::unique_ptr<AudioProvider> (*create)(fs::path const&, BackgroundRunner *);
|
std::unique_ptr<AudioProvider> (*create)(fs::path const&, BackgroundRunner *);
|
||||||
bool hidden;
|
bool hidden;
|
||||||
std::function<bool(agi::fs::path const&)> wants_to_open = [](auto p) { return false; };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const factory providers[] = {
|
const factory providers[] = {
|
||||||
|
@ -51,13 +48,13 @@ const factory providers[] = {
|
||||||
{"FFmpegSource", CreateFFmpegSourceAudioProvider, false},
|
{"FFmpegSource", CreateFFmpegSourceAudioProvider, false},
|
||||||
#endif
|
#endif
|
||||||
#ifdef WITH_AVISYNTH
|
#ifdef WITH_AVISYNTH
|
||||||
{"Avisynth", CreateAvisynthAudioProvider, false, [](auto p) { return agi::fs::HasExtension(p, "avs"); }},
|
{"Avisynth", CreateAvisynthAudioProvider, false},
|
||||||
#endif
|
#endif
|
||||||
#ifdef WITH_BESTSOURCE
|
#ifdef WITH_BESTSOURCE
|
||||||
{"BestSource", CreateBSAudioProvider, false},
|
{"BestSource", CreateBSAudioProvider, false},
|
||||||
#endif
|
#endif
|
||||||
#ifdef WITH_VAPOURSYNTH
|
#ifdef WITH_VAPOURSYNTH
|
||||||
{"VapourSynth", CreateVapourSynthAudioProvider, false, [](auto p) { return agi::fs::HasExtension(p, "py") || agi::fs::HasExtension(p, "vpy"); }},
|
{"VapourSynth", CreateVapourSynthAudioProvider, false},
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -66,83 +63,51 @@ std::vector<std::string> GetAudioProviderNames() {
|
||||||
return ::GetClasses(boost::make_iterator_range(std::begin(providers), std::end(providers)));
|
return ::GetClasses(boost::make_iterator_range(std::begin(providers), std::end(providers)));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<agi::AudioProvider> SelectAudioProvider(fs::path const& filename,
|
|
||||||
Path const& path_helper,
|
|
||||||
BackgroundRunner *br) {
|
|
||||||
auto preferred = OPT_GET("Audio/Provider")->GetString();
|
|
||||||
|
|
||||||
if (!std::any_of(std::begin(providers), std::end(providers), [&](factory provider) { return provider.name == preferred; })) {
|
|
||||||
preferred = OPT_GET("Audio/Provider")->GetDefaultString();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto sorted = GetSorted(boost::make_iterator_range(std::begin(providers), std::end(providers)), preferred);
|
|
||||||
|
|
||||||
RearrangeWithPriority(sorted, filename);
|
|
||||||
|
|
||||||
bool found_file = false;
|
|
||||||
std::string errors;
|
|
||||||
|
|
||||||
auto tried_providers = sorted.begin();
|
|
||||||
|
|
||||||
for (; tried_providers < sorted.end(); tried_providers++) {
|
|
||||||
auto factory = *tried_providers;
|
|
||||||
std::string err;
|
|
||||||
try {
|
|
||||||
auto provider = factory->create(filename, br);
|
|
||||||
if (!provider) {
|
|
||||||
err = "Failed to create provider."; // Some generic error message here
|
|
||||||
} else {
|
|
||||||
LOG_I("audio_provider") << "Using audio provider: " << factory->name;
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (AudioDataNotFound const& ex) {
|
|
||||||
found_file = true;
|
|
||||||
err = ex.GetMessage();
|
|
||||||
}
|
|
||||||
catch (AudioProviderError const& ex) {
|
|
||||||
found_file = true;
|
|
||||||
err = ex.GetMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
errors += std::string(factory->name) + ": " + err + "\n";
|
|
||||||
LOG_D("audio_provider") << factory->name << ": " << err;
|
|
||||||
if (factory->name == preferred)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<const factory *> remaining_providers(tried_providers + 1, sorted.end());
|
|
||||||
|
|
||||||
if (!remaining_providers.size()) {
|
|
||||||
// No provider could open the file
|
|
||||||
LOG_E("audio_provider") << "Could not open " << filename;
|
|
||||||
std::string msg = "Could not open " + filename.string() + ":\n" + errors;
|
|
||||||
|
|
||||||
if (!found_file) throw AudioDataNotFound(filename.string());
|
|
||||||
throw AudioProviderError(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> names;
|
|
||||||
for (auto const& f : remaining_providers)
|
|
||||||
names.push_back(f->name);
|
|
||||||
|
|
||||||
int choice = wxGetSingleChoiceIndex(agi::format("Could not open %s with the preferred provider:\n\n%s\nPlease choose a different audio provider to try:", filename.string(), errors), _("Error loading audio"), to_wx(names));
|
|
||||||
if (choice == -1) {
|
|
||||||
throw agi::UserCancelException("audio loading cancelled by user");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto factory = remaining_providers[choice];
|
|
||||||
auto provider = factory->create(filename, br);
|
|
||||||
if (!provider)
|
|
||||||
throw AudioProviderError("Audio provider returned null pointer");
|
|
||||||
LOG_I("audio_provider") << factory->name << ": opened " << filename;
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<agi::AudioProvider> GetAudioProvider(fs::path const& filename,
|
std::unique_ptr<agi::AudioProvider> GetAudioProvider(fs::path const& filename,
|
||||||
Path const& path_helper,
|
Path const& path_helper,
|
||||||
BackgroundRunner *br) {
|
BackgroundRunner *br) {
|
||||||
std::unique_ptr<agi::AudioProvider> provider = SelectAudioProvider(filename, path_helper, br);
|
auto preferred = OPT_GET("Audio/Provider")->GetString();
|
||||||
|
auto sorted = GetSorted(boost::make_iterator_range(std::begin(providers), std::end(providers)), preferred);
|
||||||
|
|
||||||
|
std::unique_ptr<AudioProvider> provider;
|
||||||
|
bool found_file = false;
|
||||||
|
bool found_audio = false;
|
||||||
|
std::string msg_all; // error messages from all attempted providers
|
||||||
|
std::string msg_partial; // error messages from providers that could partially load the file (knows container, missing codec)
|
||||||
|
|
||||||
|
for (auto const& factory : sorted) {
|
||||||
|
try {
|
||||||
|
provider = factory->create(filename, br);
|
||||||
|
if (!provider) continue;
|
||||||
|
LOG_I("audio_provider") << "Using audio provider: " << factory->name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (fs::FileNotFound const& err) {
|
||||||
|
LOG_D("audio_provider") << err.GetMessage();
|
||||||
|
msg_all += std::string(factory->name) + ": " + err.GetMessage() + " not found.\n";
|
||||||
|
}
|
||||||
|
catch (AudioDataNotFound const& err) {
|
||||||
|
LOG_D("audio_provider") << err.GetMessage();
|
||||||
|
found_file = true;
|
||||||
|
msg_all += std::string(factory->name) + ": " + err.GetMessage() + "\n";
|
||||||
|
}
|
||||||
|
catch (AudioProviderError const& err) {
|
||||||
|
LOG_D("audio_provider") << err.GetMessage();
|
||||||
|
found_audio = true;
|
||||||
|
found_file = true;
|
||||||
|
std::string thismsg = std::string(factory->name) + ": " + err.GetMessage() + "\n";
|
||||||
|
msg_all += thismsg;
|
||||||
|
msg_partial += thismsg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!provider) {
|
||||||
|
if (found_audio)
|
||||||
|
throw AudioProviderError(msg_partial);
|
||||||
|
if (found_file)
|
||||||
|
throw AudioDataNotFound(msg_all);
|
||||||
|
throw fs::FileNotFound(filename);
|
||||||
|
}
|
||||||
|
|
||||||
bool needs_cache = provider->NeedsCache();
|
bool needs_cache = provider->NeedsCache();
|
||||||
|
|
||||||
|
|
|
@ -58,11 +58,7 @@ VapourSynthAudioProvider::VapourSynthAudioProvider(agi::fs::path const& filename
|
||||||
|
|
||||||
VSCleanCache();
|
VSCleanCache();
|
||||||
|
|
||||||
VSCore *core = vs.GetAPI()->createCore(OPT_GET("Provider/VapourSynth/Autoload User Plugins")->GetBool() ? 0 : VSCoreCreationFlags::ccfDisableAutoLoading);
|
script = vs.GetScriptAPI()->createScript(nullptr);
|
||||||
if (core == nullptr) {
|
|
||||||
throw VapourSynthError("Error creating core");
|
|
||||||
}
|
|
||||||
script = vs.GetScriptAPI()->createScript(core);
|
|
||||||
if (script == nullptr) {
|
if (script == nullptr) {
|
||||||
throw VapourSynthError("Error creating script API");
|
throw VapourSynthError("Error creating script API");
|
||||||
}
|
}
|
||||||
|
@ -90,7 +86,9 @@ VapourSynthAudioProvider::VapourSynthAudioProvider(agi::fs::path const& filename
|
||||||
num_samples = vi->numSamples;
|
num_samples = vi->numSamples;
|
||||||
}
|
}
|
||||||
catch (VapourSynthError const& err) {
|
catch (VapourSynthError const& err) {
|
||||||
throw agi::AudioProviderError(err.GetMessage());
|
// Unlike the video provider manager, the audio provider factory catches AudioProviderErrors and picks whichever source doesn't throw one.
|
||||||
|
// So just rethrow the Error here with an extra label so the user will see the error message and know the audio wasn't loaded with VS
|
||||||
|
throw VapourSynthError(agi::format("VapourSynth error: %s", err.GetMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|
|
@ -216,7 +216,6 @@ namespace Automation4 {
|
||||||
wxWindow *ww = config_dialog->CreateWindow(&w); // generate actual dialog contents
|
wxWindow *ww = config_dialog->CreateWindow(&w); // generate actual dialog contents
|
||||||
s->Add(ww, 0, wxALL, 5); // add contents to dialog
|
s->Add(ww, 0, wxALL, 5); // add contents to dialog
|
||||||
w.SetSizerAndFit(s);
|
w.SetSizerAndFit(s);
|
||||||
w.SetLayoutAdaptationMode(wxDIALOG_ADAPTATION_MODE_ENABLED);
|
|
||||||
w.CenterOnParent();
|
w.CenterOnParent();
|
||||||
w.ShowModal();
|
w.ShowModal();
|
||||||
});
|
});
|
||||||
|
@ -316,14 +315,11 @@ namespace Automation4 {
|
||||||
|
|
||||||
std::vector<std::future<std::unique_ptr<Script>>> script_futures;
|
std::vector<std::future<std::unique_ptr<Script>>> script_futures;
|
||||||
|
|
||||||
std::set<agi::fs::path> dirnames;
|
auto path_it = agi::Split(path, '|');
|
||||||
for (auto tok : agi::Split(path, '|')) {
|
for (auto tok : std::set<agi::StringRange>(begin(path_it), end(path_it))) {
|
||||||
auto dirname = config::path->Decode(agi::str(tok));
|
auto dirname = config::path->Decode(agi::str(tok));
|
||||||
if (!agi::fs::DirectoryExists(dirname)) continue;
|
if (!agi::fs::DirectoryExists(dirname)) continue;
|
||||||
|
|
||||||
if (dirnames.count(dirname)) continue;
|
|
||||||
dirnames.insert(dirname);
|
|
||||||
|
|
||||||
for (auto filename : agi::fs::DirectoryIterator(dirname, "*.*"))
|
for (auto filename : agi::fs::DirectoryIterator(dirname, "*.*"))
|
||||||
script_futures.emplace_back(std::async(std::launch::async, [=] {
|
script_futures.emplace_back(std::async(std::launch::async, [=] {
|
||||||
return ScriptFactory::CreateFromFile(dirname/filename, false, false);
|
return ScriptFactory::CreateFromFile(dirname/filename, false, false);
|
||||||
|
|
|
@ -198,25 +198,25 @@ namespace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<VideoFrame> *check_VideoFrame(lua_State *L) {
|
std::shared_ptr<VideoFrame> check_VideoFrame(lua_State *L) {
|
||||||
auto framePtr = static_cast<std::shared_ptr<VideoFrame>*>(luaL_checkudata(L, 1, "VideoFrame"));
|
auto framePtr = static_cast<std::shared_ptr<VideoFrame>*>(luaL_checkudata(L, 1, "VideoFrame"));
|
||||||
return framePtr;
|
return *framePtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int FrameWidth(lua_State *L) {
|
int FrameWidth(lua_State *L) {
|
||||||
std::shared_ptr<VideoFrame> frame = *check_VideoFrame(L);
|
std::shared_ptr<VideoFrame> frame = check_VideoFrame(L);
|
||||||
push_value(L, frame->width);
|
push_value(L, frame->width);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int FrameHeight(lua_State *L) {
|
int FrameHeight(lua_State *L) {
|
||||||
std::shared_ptr<VideoFrame> frame = *check_VideoFrame(L);
|
std::shared_ptr<VideoFrame> frame = check_VideoFrame(L);
|
||||||
push_value(L, frame->height);
|
push_value(L, frame->height);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int FramePixel(lua_State *L) {
|
int FramePixel(lua_State *L) {
|
||||||
std::shared_ptr<VideoFrame> frame = *check_VideoFrame(L);
|
std::shared_ptr<VideoFrame> frame = check_VideoFrame(L);
|
||||||
size_t x = lua_tointeger(L, -2);
|
size_t x = lua_tointeger(L, -2);
|
||||||
size_t y = lua_tointeger(L, -1);
|
size_t y = lua_tointeger(L, -1);
|
||||||
lua_pop(L, 2);
|
lua_pop(L, 2);
|
||||||
|
@ -227,17 +227,16 @@ namespace {
|
||||||
|
|
||||||
size_t pos = y * frame->pitch + x * 4;
|
size_t pos = y * frame->pitch + x * 4;
|
||||||
// VideoFrame is stored as BGRA, but we want to return RGB
|
// VideoFrame is stored as BGRA, but we want to return RGB
|
||||||
push_value(L, frame->data[pos+2]);
|
int pixelValue = frame->data[pos+2] * 65536 + frame->data[pos+1] * 256 + frame->data[pos];
|
||||||
push_value(L, frame->data[pos+1]);
|
push_value(L, pixelValue);
|
||||||
push_value(L, frame->data[pos]);
|
|
||||||
} else {
|
} else {
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
}
|
}
|
||||||
return 3;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int FramePixelFormatted(lua_State *L) {
|
int FramePixelFormatted(lua_State *L) {
|
||||||
std::shared_ptr<VideoFrame> frame = *check_VideoFrame(L);
|
std::shared_ptr<VideoFrame> frame = check_VideoFrame(L);
|
||||||
size_t x = lua_tointeger(L, -2);
|
size_t x = lua_tointeger(L, -2);
|
||||||
size_t y = lua_tointeger(L, -1);
|
size_t y = lua_tointeger(L, -1);
|
||||||
lua_pop(L, 2);
|
lua_pop(L, 2);
|
||||||
|
@ -256,19 +255,9 @@ namespace {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int FrameData(lua_State *L) {
|
int FrameDestory(lua_State *L) {
|
||||||
std::shared_ptr<VideoFrame> frame = *check_VideoFrame(L);
|
std::shared_ptr<VideoFrame> frame = check_VideoFrame(L);
|
||||||
|
frame.~shared_ptr<VideoFrame>();
|
||||||
push_value(L, frame->data.data());
|
|
||||||
push_value(L, frame->pitch);
|
|
||||||
push_value(L, frame->flipped);
|
|
||||||
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
int FrameDestroy(lua_State *L) {
|
|
||||||
std::shared_ptr<VideoFrame> *frame = check_VideoFrame(L);
|
|
||||||
frame->~shared_ptr<VideoFrame>();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,8 +279,7 @@ namespace {
|
||||||
{"height", FrameHeight},
|
{"height", FrameHeight},
|
||||||
{"getPixel", FramePixel},
|
{"getPixel", FramePixel},
|
||||||
{"getPixelFormatted", FramePixelFormatted},
|
{"getPixelFormatted", FramePixelFormatted},
|
||||||
{"data", FrameData},
|
{"__gc", FrameDestory},
|
||||||
{"__gc", FrameDestroy},
|
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -689,7 +677,6 @@ namespace {
|
||||||
if (lua_isnumber(L, -1) && lua_tointeger(L, -1) == 3) {
|
if (lua_isnumber(L, -1) && lua_tointeger(L, -1) == 3) {
|
||||||
lua_pop(L, 1); // just to avoid tripping the stackcheck in debug
|
lua_pop(L, 1); // just to avoid tripping the stackcheck in debug
|
||||||
description = "Attempted to load an Automation 3 script as an Automation 4 Lua script. Automation 3 is no longer supported.";
|
description = "Attempted to load an Automation 3 script as an Automation 4 Lua script. Automation 3 is no longer supported.";
|
||||||
stackcheck.check_stack(0);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -702,7 +689,6 @@ namespace {
|
||||||
name = GetPrettyFilename().string();
|
name = GetPrettyFilename().string();
|
||||||
|
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
stackcheck.check_stack(0);
|
|
||||||
// if we got this far, the script should be ready
|
// if we got this far, the script should be ready
|
||||||
loaded = true;
|
loaded = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,8 +78,6 @@ namespace Automation4 {
|
||||||
std::deque<PendingCommit> pending_commits;
|
std::deque<PendingCommit> pending_commits;
|
||||||
/// Lines to delete once processing complete successfully
|
/// Lines to delete once processing complete successfully
|
||||||
std::vector<std::unique_ptr<AssEntry>> lines_to_delete;
|
std::vector<std::unique_ptr<AssEntry>> lines_to_delete;
|
||||||
/// Lines that were allocated here and need to be deleted if the script is cancelled.
|
|
||||||
std::vector<AssEntry *> allocated_lines;
|
|
||||||
|
|
||||||
/// Create copies of all of the lines in the script info section if it
|
/// Create copies of all of the lines in the script info section if it
|
||||||
/// hasn't already happened. This is done lazily, since it only needs
|
/// hasn't already happened. This is done lazily, since it only needs
|
||||||
|
@ -120,8 +118,6 @@ namespace Automation4 {
|
||||||
/// assumes a Lua representation of AssEntry on the top of the stack, and creates an AssEntry object of it
|
/// assumes a Lua representation of AssEntry on the top of the stack, and creates an AssEntry object of it
|
||||||
static std::unique_ptr<AssEntry> LuaToAssEntry(lua_State *L, AssFile *ass=nullptr);
|
static std::unique_ptr<AssEntry> LuaToAssEntry(lua_State *L, AssFile *ass=nullptr);
|
||||||
|
|
||||||
std::unique_ptr<AssEntry> LuaToTrackedAssEntry(lua_State *L);
|
|
||||||
|
|
||||||
/// @brief Signal that the script using this file is now done running
|
/// @brief Signal that the script using this file is now done running
|
||||||
/// @param set_undo If there's any uncommitted changes to the file,
|
/// @param set_undo If there's any uncommitted changes to the file,
|
||||||
/// they will be automatically committed with this
|
/// they will be automatically committed with this
|
||||||
|
|
|
@ -328,12 +328,6 @@ namespace Automation4 {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AssEntry> LuaAssFile::LuaToTrackedAssEntry(lua_State *L) {
|
|
||||||
std::unique_ptr<AssEntry> e = LuaToAssEntry(L, ass);
|
|
||||||
allocated_lines.push_back(e.get());
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
int LuaAssFile::ObjectIndexRead(lua_State *L)
|
int LuaAssFile::ObjectIndexRead(lua_State *L)
|
||||||
{
|
{
|
||||||
switch (lua_type(L, 2)) {
|
switch (lua_type(L, 2)) {
|
||||||
|
@ -459,7 +453,7 @@ namespace Automation4 {
|
||||||
// insert
|
// insert
|
||||||
CheckBounds(n);
|
CheckBounds(n);
|
||||||
|
|
||||||
auto e = LuaToTrackedAssEntry(L);
|
auto e = LuaToAssEntry(L, ass);
|
||||||
modification_type |= modification_mask(e.get());
|
modification_type |= modification_mask(e.get());
|
||||||
QueueLineForDeletion(n - 1);
|
QueueLineForDeletion(n - 1);
|
||||||
AssignLine(n - 1, std::move(e));
|
AssignLine(n - 1, std::move(e));
|
||||||
|
@ -548,7 +542,7 @@ namespace Automation4 {
|
||||||
|
|
||||||
for (int i = 1; i <= n; i++) {
|
for (int i = 1; i <= n; i++) {
|
||||||
lua_pushvalue(L, i);
|
lua_pushvalue(L, i);
|
||||||
auto e = LuaToTrackedAssEntry(L);
|
auto e = LuaToAssEntry(L, ass);
|
||||||
modification_type |= modification_mask(e.get());
|
modification_type |= modification_mask(e.get());
|
||||||
|
|
||||||
if (lines.empty()) {
|
if (lines.empty()) {
|
||||||
|
@ -592,7 +586,7 @@ namespace Automation4 {
|
||||||
new_entries.reserve(n - 1);
|
new_entries.reserve(n - 1);
|
||||||
for (int i = 2; i <= n; i++) {
|
for (int i = 2; i <= n; i++) {
|
||||||
lua_pushvalue(L, i);
|
lua_pushvalue(L, i);
|
||||||
auto e = LuaToTrackedAssEntry(L);
|
auto e = LuaToAssEntry(L, ass);
|
||||||
modification_type |= modification_mask(e.get());
|
modification_type |= modification_mask(e.get());
|
||||||
InsertLine(new_entries, i - 2, std::move(e));
|
InsertLine(new_entries, i - 2, std::move(e));
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
@ -740,7 +734,6 @@ namespace Automation4 {
|
||||||
void LuaAssFile::Cancel()
|
void LuaAssFile::Cancel()
|
||||||
{
|
{
|
||||||
for (auto& line : lines_to_delete) line.release();
|
for (auto& line : lines_to_delete) line.release();
|
||||||
for (AssEntry *line : allocated_lines) delete line;
|
|
||||||
references--;
|
references--;
|
||||||
if (!references) delete this;
|
if (!references) delete this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -326,12 +326,6 @@ namespace Automation4 {
|
||||||
{
|
{
|
||||||
lua_getfield(L, -1, "items");
|
lua_getfield(L, -1, "items");
|
||||||
read_string_array(L, items);
|
read_string_array(L, items);
|
||||||
|
|
||||||
#ifdef __WXMAC__
|
|
||||||
if (std::find(items.begin(), items.end(), value) == items.end()) {
|
|
||||||
items.insert(items.begin(), value);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CanSerialiseValue() const override { return true; }
|
bool CanSerialiseValue() const override { return true; }
|
||||||
|
|
|
@ -53,7 +53,6 @@
|
||||||
// Allocate storage for and initialise static members
|
// Allocate storage for and initialise static members
|
||||||
namespace {
|
namespace {
|
||||||
int avs_refcount = 0;
|
int avs_refcount = 0;
|
||||||
bool failed = false;
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
HINSTANCE hLib = nullptr;
|
HINSTANCE hLib = nullptr;
|
||||||
#else
|
#else
|
||||||
|
@ -67,8 +66,8 @@ const AVS_Linkage *AVS_linkage = nullptr;
|
||||||
|
|
||||||
typedef IScriptEnvironment* __stdcall FUNC(int);
|
typedef IScriptEnvironment* __stdcall FUNC(int);
|
||||||
|
|
||||||
AviSynthWrapper::AviSynthWrapper() try {
|
AviSynthWrapper::AviSynthWrapper() {
|
||||||
if (!avs_refcount++) {
|
if (!avs_refcount){
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define CONCATENATE(x, y) x ## y
|
#define CONCATENATE(x, y) x ## y
|
||||||
#define _Lstr(x) CONCATENATE(L, x)
|
#define _Lstr(x) CONCATENATE(L, x)
|
||||||
|
@ -95,6 +94,8 @@ AviSynthWrapper::AviSynthWrapper() try {
|
||||||
if (!env)
|
if (!env)
|
||||||
throw AvisynthError("Failed to create a new avisynth script environment. Avisynth is too old?");
|
throw AvisynthError("Failed to create a new avisynth script environment. Avisynth is too old?");
|
||||||
|
|
||||||
|
avs_refcount++;
|
||||||
|
|
||||||
AVS_linkage = env->GetAVSLinkage();
|
AVS_linkage = env->GetAVSLinkage();
|
||||||
|
|
||||||
// Set memory limit
|
// Set memory limit
|
||||||
|
@ -102,9 +103,6 @@ AviSynthWrapper::AviSynthWrapper() try {
|
||||||
if (memoryMax)
|
if (memoryMax)
|
||||||
env->SetMemoryMax(memoryMax);
|
env->SetMemoryMax(memoryMax);
|
||||||
}
|
}
|
||||||
} catch (AvisynthError const&) {
|
|
||||||
avs_refcount--;
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AviSynthWrapper::~AviSynthWrapper() {
|
AviSynthWrapper::~AviSynthWrapper() {
|
||||||
|
|
|
@ -56,14 +56,13 @@
|
||||||
#include <wx/scrolbar.h>
|
#include <wx/scrolbar.h>
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
|
|
||||||
// Check menu.h for id range allocation before editing this enum
|
|
||||||
enum {
|
enum {
|
||||||
GRID_SCROLLBAR = 1730,
|
GRID_SCROLLBAR = 1730,
|
||||||
MENU_SHOW_COL = (wxID_HIGHEST + 1) + 2000 // Needs 15 IDs after this
|
MENU_SHOW_COL = 1250 // Needs 15 IDs after this
|
||||||
};
|
};
|
||||||
|
|
||||||
BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context)
|
BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context)
|
||||||
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | (OPT_GET("App/Dark Mode")->GetBool() ? wxBORDER_SIMPLE : wxSUNKEN_BORDER))
|
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxSUNKEN_BORDER)
|
||||||
, scrollBar(new wxScrollBar(this, GRID_SCROLLBAR, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL))
|
, scrollBar(new wxScrollBar(this, GRID_SCROLLBAR, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL))
|
||||||
, context(context)
|
, context(context)
|
||||||
, columns(GetGridColumns())
|
, columns(GetGridColumns())
|
||||||
|
@ -134,10 +133,14 @@ void BaseGrid::OnSubtitlesCommit(int type) {
|
||||||
if (type == AssFile::COMMIT_NEW || type & AssFile::COMMIT_ORDER || type & AssFile::COMMIT_DIAG_ADDREM || type & AssFile::COMMIT_FOLD)
|
if (type == AssFile::COMMIT_NEW || type & AssFile::COMMIT_ORDER || type & AssFile::COMMIT_DIAG_ADDREM || type & AssFile::COMMIT_FOLD)
|
||||||
UpdateMaps();
|
UpdateMaps();
|
||||||
|
|
||||||
if (type & AssFile::COMMIT_DIAG_META || type & AssFile::COMMIT_DIAG_TIME) {
|
if (type & AssFile::COMMIT_DIAG_META) {
|
||||||
SetColumnWidths();
|
SetColumnWidths();
|
||||||
Refresh(false);
|
Refresh(false);
|
||||||
} else if (type & AssFile::COMMIT_DIAG_TEXT) {
|
return;
|
||||||
|
}
|
||||||
|
if (type & AssFile::COMMIT_DIAG_TIME)
|
||||||
|
Refresh(false);
|
||||||
|
else if (type & AssFile::COMMIT_DIAG_TEXT) {
|
||||||
for (auto const& rect : text_refresh_rects)
|
for (auto const& rect : text_refresh_rects)
|
||||||
RefreshRect(rect, false);
|
RefreshRect(rect, false);
|
||||||
}
|
}
|
||||||
|
@ -576,7 +579,7 @@ void BaseGrid::OnMouseEvent(wxMouseEvent &event) {
|
||||||
void BaseGrid::OnContextMenu(wxContextMenuEvent &evt) {
|
void BaseGrid::OnContextMenu(wxContextMenuEvent &evt) {
|
||||||
wxPoint pos = evt.GetPosition();
|
wxPoint pos = evt.GetPosition();
|
||||||
if (pos == wxDefaultPosition || ScreenToClient(pos).y > lineHeight) {
|
if (pos == wxDefaultPosition || ScreenToClient(pos).y > lineHeight) {
|
||||||
if (!context_menu) context_menu = menu::GetMenu("grid_context", (wxID_HIGHEST + 1) + 8000, context);
|
if (!context_menu) context_menu = menu::GetMenu("grid_context", context);
|
||||||
menu::OpenPopupMenu(context_menu.get(), this);
|
menu::OpenPopupMenu(context_menu.get(), this);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -21,13 +21,7 @@
|
||||||
|
|
||||||
#ifdef WITH_BESTSOURCE
|
#ifdef WITH_BESTSOURCE
|
||||||
#include "bestsource_common.h"
|
#include "bestsource_common.h"
|
||||||
#include "tracklist.h"
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include <libavutil/avutil.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "format.h"
|
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
@ -37,70 +31,26 @@ extern "C" {
|
||||||
#include <boost/crc.hpp>
|
#include <boost/crc.hpp>
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
|
|
||||||
namespace provider_bs {
|
|
||||||
|
|
||||||
std::pair<TrackSelection, bool> SelectTrack(agi::fs::path const& filename, bool audio) {
|
std::string GetBSCacheFile(agi::fs::path const& filename) {
|
||||||
std::map<std::string, std::string> opts;
|
// BS can store all its index data in a single file, but we make a separate index file
|
||||||
BestTrackList tracklist(filename.string(), &opts);
|
// for each video file to ensure that the old index is invalidated if the file is modified.
|
||||||
|
// While BS does check the filesize of the files, it doesn't check the modification time.
|
||||||
int n = tracklist.GetNumTracks();
|
uintmax_t len = agi::fs::Size(filename);
|
||||||
AVMediaType type = audio ? AVMEDIA_TYPE_AUDIO : AVMEDIA_TYPE_VIDEO;
|
|
||||||
|
|
||||||
std::vector<int> TrackNumbers;
|
|
||||||
wxArrayString Choices;
|
|
||||||
bool has_audio = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
BestTrackList::TrackInfo info = tracklist.GetTrackInfo(i);
|
|
||||||
has_audio = has_audio || (info.MediaType == AVMEDIA_TYPE_AUDIO);
|
|
||||||
|
|
||||||
if (info.MediaType == type) {
|
|
||||||
TrackNumbers.push_back(i);
|
|
||||||
Choices.Add(agi::wxformat(_("Track %02d: %s"), i, info.CodecString));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackSelection result;
|
|
||||||
|
|
||||||
if (TrackNumbers.empty()) {
|
|
||||||
result = TrackSelection::NoTracks;
|
|
||||||
} else if (TrackNumbers.size() == 1) {
|
|
||||||
result = static_cast<TrackSelection>(TrackNumbers[0]);
|
|
||||||
} else {
|
|
||||||
int Choice = wxGetSingleChoiceIndex(
|
|
||||||
audio ? _("Multiple audio tracks detected, please choose the one you wish to load:") : _("Multiple video tracks detected, please choose the one you wish to load:"),
|
|
||||||
audio ? _("Choose audio track") : _("Choose video track"),
|
|
||||||
Choices);
|
|
||||||
|
|
||||||
if (Choice >= 0)
|
|
||||||
result = static_cast<TrackSelection>(TrackNumbers[Choice]) ;
|
|
||||||
else
|
|
||||||
result = TrackSelection::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_pair(result, has_audio);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GetCacheFile(agi::fs::path const& filename) {
|
|
||||||
boost::crc_32_type hash;
|
boost::crc_32_type hash;
|
||||||
hash.process_bytes(filename.string().c_str(), filename.string().size());
|
hash.process_bytes(filename.string().c_str(), filename.string().size());
|
||||||
|
|
||||||
auto result = config::path->Decode("?local/bsindex/" + filename.filename().string() + "_" + std::to_string(hash.checksum()) + "_" + std::to_string(agi::fs::ModifiedTime(filename)));
|
auto result = config::path->Decode("?local/bsindex/" + filename.filename().string() + "_" + std::to_string(hash.checksum()) + "_" + std::to_string(len) + "_" + std::to_string(agi::fs::ModifiedTime(filename)) + ".json");
|
||||||
agi::fs::CreateDirectory(result.parent_path());
|
agi::fs::CreateDirectory(result.parent_path());
|
||||||
|
|
||||||
return result.string();
|
return result.string();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CleanBSCache() {
|
void BSCleanCache() {
|
||||||
CleanCache(config::path->Decode("?local/bsindex/"),
|
CleanCache(config::path->Decode("?local/bsindex/"),
|
||||||
"*.bsindex",
|
"*.json",
|
||||||
OPT_GET("Provider/BestSource/Cache/Size")->GetInt(),
|
OPT_GET("Provider/BestSource/Cache/Size")->GetInt(),
|
||||||
OPT_GET("Provider/BestSource/Cache/Files")->GetInt());
|
OPT_GET("Provider/BestSource/Cache/Files")->GetInt());
|
||||||
|
|
||||||
// Delete old cache files: TODO remove this after a while
|
|
||||||
CleanCache(config::path->Decode("?local/bsindex/"),
|
|
||||||
"*.json", 0, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // WITH_BESTSOURCE
|
#endif // WITH_BESTSOURCE
|
||||||
|
|
|
@ -21,25 +21,9 @@
|
||||||
|
|
||||||
#ifdef WITH_BESTSOURCE
|
#ifdef WITH_BESTSOURCE
|
||||||
|
|
||||||
#include <bsshared.h>
|
|
||||||
|
|
||||||
#include <libaegisub/fs_fwd.h>
|
#include <libaegisub/fs_fwd.h>
|
||||||
#include <libaegisub/background_runner.h>
|
|
||||||
|
|
||||||
namespace provider_bs {
|
std::string GetBSCacheFile(agi::fs::path const& filename);
|
||||||
|
void BSCleanCache();
|
||||||
// X11 memes
|
|
||||||
#undef None
|
|
||||||
|
|
||||||
enum class TrackSelection : int {
|
|
||||||
None = -1,
|
|
||||||
NoTracks = -2,
|
|
||||||
};
|
|
||||||
|
|
||||||
std::pair<TrackSelection, bool> SelectTrack(agi::fs::path const& filename, bool audio);
|
|
||||||
std::string GetCacheFile(agi::fs::path const& filename);
|
|
||||||
void CleanBSCache();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* WITH_BESTSOURCE */
|
#endif /* WITH_BESTSOURCE */
|
||||||
|
|
|
@ -18,7 +18,11 @@
|
||||||
|
|
||||||
#include "dialogs.h"
|
#include "dialogs.h"
|
||||||
|
|
||||||
|
#if BOOST_VERSION >= 106900
|
||||||
#include <boost/gil.hpp>
|
#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);
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ struct tool_assdraw final : public Command {
|
||||||
STR_HELP("Launch the ASSDraw3 tool for vector drawing")
|
STR_HELP("Launch the ASSDraw3 tool for vector drawing")
|
||||||
|
|
||||||
void operator()(agi::Context *) override {
|
void operator()(agi::Context *) override {
|
||||||
wxExecute("\"" + config::path->Decode("?data/ASSDraw3.exe").string() + "\"");
|
wxExecute("\"" + config::path->Decode("?data/ASSDraw3.exe").wstring() + "\"");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -666,8 +666,8 @@ struct video_opt_autoscroll final : public Command {
|
||||||
|
|
||||||
struct video_pan_reset final : public validator_video_loaded {
|
struct video_pan_reset final : public validator_video_loaded {
|
||||||
CMD_NAME("video/pan_reset")
|
CMD_NAME("video/pan_reset")
|
||||||
STR_MENU("Reset Video Pan")
|
STR_MENU("Reset video pan")
|
||||||
STR_DISP("Reset Video Pan")
|
STR_DISP("Reset video pan")
|
||||||
STR_HELP("Reset the video pan to the original value")
|
STR_HELP("Reset the video pan to the original value")
|
||||||
|
|
||||||
void operator()(agi::Context *c) override {
|
void operator()(agi::Context *c) override {
|
||||||
|
|
|
@ -178,7 +178,7 @@ namespace {
|
||||||
STR_HELP("When the surrounding plane is also visible, switches which quad is locked. If inactive, the inner quad can only be resized without changing the perspective plane. If active, this holds for the outer quad instead.")
|
STR_HELP("When the surrounding plane is also visible, switches which quad is locked. If inactive, the inner quad can only be resized without changing the perspective plane. If active, this holds for the outer quad instead.")
|
||||||
|
|
||||||
bool Validate(const agi::Context *c) override {
|
bool Validate(const agi::Context *c) override {
|
||||||
return c->videoDisplay->ToolIsType(typeid(VisualToolPerspective)) && c->videoDisplay->GetSubTool() & PERSP_OUTER;
|
return c->videoDisplay->ToolIsType(typeid(VisualToolPerspective)) && c->videoDisplay->GetSubTool() | PERSP_OUTER;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -410,8 +410,8 @@ void ColorPickerScreenDropper::DropFromScreenXY(int x, int y) {
|
||||||
CGGetDisplaysWithPoint(CGPointMake(x, y), 1, &display_id, &display_count);
|
CGGetDisplaysWithPoint(CGPointMake(x, y), 1, &display_id, &display_count);
|
||||||
|
|
||||||
agi::scoped_holder<CGImageRef> img(CGDisplayCreateImageForRect(display_id, CGRectMake(x - resx / 2, y - resy / 2, resx, resy)), CGImageRelease);
|
agi::scoped_holder<CGImageRef> img(CGDisplayCreateImageForRect(display_id, CGRectMake(x - resx / 2, y - resy / 2, resx, resy)), CGImageRelease);
|
||||||
size_t width = CGImageGetWidth(img);
|
NSUInteger width = CGImageGetWidth(img);
|
||||||
size_t height = CGImageGetHeight(img);
|
NSUInteger height = CGImageGetHeight(img);
|
||||||
std::vector<uint8_t> imgdata(height * width * 4);
|
std::vector<uint8_t> imgdata(height * width * 4);
|
||||||
|
|
||||||
agi::scoped_holder<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB(), CGColorSpaceRelease);
|
agi::scoped_holder<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB(), CGColorSpaceRelease);
|
||||||
|
@ -596,7 +596,7 @@ DialogColorPicker::DialogColorPicker(wxWindow *parent, agi::Color initial_color,
|
||||||
|
|
||||||
eyedropper_bitmap = GETIMAGE(eyedropper_tool_24);
|
eyedropper_bitmap = GETIMAGE(eyedropper_tool_24);
|
||||||
eyedropper_bitmap.SetMask(new wxMask(eyedropper_bitmap, wxColour(255, 0, 255)));
|
eyedropper_bitmap.SetMask(new wxMask(eyedropper_bitmap, wxColour(255, 0, 255)));
|
||||||
screen_dropper_icon = new wxStaticBitmap(this, -1, eyedropper_bitmap, wxDefaultPosition, wxDefaultSize, (OPT_GET("App/Dark Mode")->GetBool() ? wxBORDER_SIMPLE : wxRAISED_BORDER));
|
screen_dropper_icon = new wxStaticBitmap(this, -1, eyedropper_bitmap, wxDefaultPosition, wxDefaultSize, wxRAISED_BORDER);
|
||||||
screen_dropper = new ColorPickerScreenDropper(this, 7, 7, 8);
|
screen_dropper = new ColorPickerScreenDropper(this, 7, 7, 8);
|
||||||
|
|
||||||
// Arrange the controls in a nice way
|
// Arrange the controls in a nice way
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
#include "colour_button.h"
|
#include "colour_button.h"
|
||||||
#include "compat.h"
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
#include "help_button.h"
|
#include "help_button.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
|
@ -41,7 +40,7 @@ namespace {
|
||||||
struct DialogDummyVideo {
|
struct DialogDummyVideo {
|
||||||
wxDialog d;
|
wxDialog d;
|
||||||
|
|
||||||
wxString fps = OPT_GET("Video/Dummy/FPS String")->GetString();
|
double fps = OPT_GET("Video/Dummy/FPS")->GetDouble();
|
||||||
int width = OPT_GET("Video/Dummy/Last/Width")->GetInt();
|
int width = OPT_GET("Video/Dummy/Last/Width")->GetInt();
|
||||||
int height = OPT_GET("Video/Dummy/Last/Height")->GetInt();
|
int height = OPT_GET("Video/Dummy/Last/Height")->GetInt();
|
||||||
int length = OPT_GET("Video/Dummy/Last/Length")->GetInt();
|
int length = OPT_GET("Video/Dummy/Last/Length")->GetInt();
|
||||||
|
@ -55,7 +54,7 @@ struct DialogDummyVideo {
|
||||||
void AddCtrl(wxString const& label, T *ctrl);
|
void AddCtrl(wxString const& label, T *ctrl);
|
||||||
|
|
||||||
void OnResolutionShortcut(wxCommandEvent &evt);
|
void OnResolutionShortcut(wxCommandEvent &evt);
|
||||||
bool UpdateLengthDisplay();
|
void UpdateLengthDisplay();
|
||||||
|
|
||||||
DialogDummyVideo(wxWindow *parent);
|
DialogDummyVideo(wxWindow *parent);
|
||||||
};
|
};
|
||||||
|
@ -86,6 +85,11 @@ wxSpinCtrl *spin_ctrl(wxWindow *parent, int min, int max, int *value) {
|
||||||
return ctrl;
|
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, wxDefaultSize, 0, DoubleValidator(value, min, max));
|
||||||
|
}
|
||||||
|
|
||||||
wxComboBox *resolution_shortcuts(wxWindow *parent, int width, int height) {
|
wxComboBox *resolution_shortcuts(wxWindow *parent, int width, int height) {
|
||||||
wxComboBox *ctrl = new wxComboBox(parent, -1, "", wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY);
|
wxComboBox *ctrl = new wxComboBox(parent, -1, "", wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY);
|
||||||
|
|
||||||
|
@ -117,9 +121,7 @@ DialogDummyVideo::DialogDummyVideo(wxWindow *parent)
|
||||||
AddCtrl(_("Video resolution:"), resolution_shortcuts(&d, width, height));
|
AddCtrl(_("Video resolution:"), resolution_shortcuts(&d, width, height));
|
||||||
AddCtrl("", res_sizer);
|
AddCtrl("", res_sizer);
|
||||||
AddCtrl(_("Color:"), color_sizer);
|
AddCtrl(_("Color:"), color_sizer);
|
||||||
wxTextValidator fpsVal(wxFILTER_INCLUDE_CHAR_LIST, &fps);
|
AddCtrl(_("Frame rate (fps):"), spin_ctrl(&d, .1, 1000.0, &fps));
|
||||||
fpsVal.SetCharIncludes("0123456789./");
|
|
||||||
AddCtrl(_("Frame rate (fps):"), new wxTextCtrl(&d, -1, "", wxDefaultPosition, wxDefaultSize, 0, fpsVal));
|
|
||||||
AddCtrl(_("Duration (frames):"), spin_ctrl(&d, 2, 36000000, &length)); // Ten hours of 1k FPS
|
AddCtrl(_("Duration (frames):"), spin_ctrl(&d, 2, 36000000, &length)); // Ten hours of 1k FPS
|
||||||
AddCtrl("", length_display = new wxStaticText(&d, -1, ""));
|
AddCtrl("", length_display = new wxStaticText(&d, -1, ""));
|
||||||
|
|
||||||
|
@ -131,19 +133,17 @@ DialogDummyVideo::DialogDummyVideo(wxWindow *parent)
|
||||||
main_sizer->Add(new wxStaticLine(&d, wxHORIZONTAL), wxSizerFlags().HorzBorder().Expand());
|
main_sizer->Add(new wxStaticLine(&d, wxHORIZONTAL), wxSizerFlags().HorzBorder().Expand());
|
||||||
main_sizer->Add(btn_sizer, wxSizerFlags().Expand().Border());
|
main_sizer->Add(btn_sizer, wxSizerFlags().Expand().Border());
|
||||||
|
|
||||||
btn_sizer->GetAffirmativeButton()->Enable(UpdateLengthDisplay());
|
UpdateLengthDisplay();
|
||||||
|
|
||||||
d.SetSizerAndFit(main_sizer);
|
d.SetSizerAndFit(main_sizer);
|
||||||
d.CenterOnParent();
|
d.CenterOnParent();
|
||||||
|
|
||||||
d.Bind(wxEVT_COMBOBOX, &DialogDummyVideo::OnResolutionShortcut, this);
|
d.Bind(wxEVT_COMBOBOX, &DialogDummyVideo::OnResolutionShortcut, this);
|
||||||
color_btn->Bind(EVT_COLOR, [=](ValueEvent<agi::Color>& e) { color = e.Get(); });
|
color_btn->Bind(EVT_COLOR, [=](ValueEvent<agi::Color>& e) { color = e.Get(); });
|
||||||
auto on_update = [&, btn_sizer](wxCommandEvent&) {
|
d.Bind(wxEVT_SPINCTRL, [&](wxCommandEvent&) {
|
||||||
d.TransferDataFromWindow();
|
d.TransferDataFromWindow();
|
||||||
btn_sizer->GetAffirmativeButton()->Enable(UpdateLengthDisplay());
|
UpdateLengthDisplay();
|
||||||
};
|
});
|
||||||
d.Bind(wxEVT_SPINCTRL, on_update);
|
|
||||||
d.Bind(wxEVT_TEXT, on_update);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_label(wxWindow *parent, wxSizer *sizer, wxString const& label) {
|
static void add_label(wxWindow *parent, wxSizer *sizer, wxString const& label) {
|
||||||
|
@ -167,16 +167,8 @@ void DialogDummyVideo::OnResolutionShortcut(wxCommandEvent &e) {
|
||||||
d.TransferDataToWindow();
|
d.TransferDataToWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DialogDummyVideo::UpdateLengthDisplay() {
|
void DialogDummyVideo::UpdateLengthDisplay() {
|
||||||
std::string dur = "-";
|
length_display->SetLabel(fmt_tl("Resulting duration: %s", agi::Time(length / fps * 1000).GetAssFormatted(true)));
|
||||||
bool valid = false;
|
|
||||||
agi::vfr::Framerate fr;
|
|
||||||
if (DummyVideoProvider::TryParseFramerate(from_wx(fps), fr)) {
|
|
||||||
dur = agi::Time(fr.TimeAtFrame(length)).GetAssFormatted(true);
|
|
||||||
valid = true;
|
|
||||||
}
|
|
||||||
length_display->SetLabel(fmt_tl("Resulting duration: %s", dur));
|
|
||||||
return valid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,12 +177,12 @@ std::string CreateDummyVideo(wxWindow *parent) {
|
||||||
if (dlg.d.ShowModal() != wxID_OK)
|
if (dlg.d.ShowModal() != wxID_OK)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
OPT_SET("Video/Dummy/FPS String")->SetString(from_wx(dlg.fps));
|
OPT_SET("Video/Dummy/FPS")->SetDouble(dlg.fps);
|
||||||
OPT_SET("Video/Dummy/Last/Width")->SetInt(dlg.width);
|
OPT_SET("Video/Dummy/Last/Width")->SetInt(dlg.width);
|
||||||
OPT_SET("Video/Dummy/Last/Height")->SetInt(dlg.height);
|
OPT_SET("Video/Dummy/Last/Height")->SetInt(dlg.height);
|
||||||
OPT_SET("Video/Dummy/Last/Length")->SetInt(dlg.length);
|
OPT_SET("Video/Dummy/Last/Length")->SetInt(dlg.length);
|
||||||
OPT_SET("Colour/Video Dummy/Last Colour")->SetColor(dlg.color);
|
OPT_SET("Colour/Video Dummy/Last Colour")->SetColor(dlg.color);
|
||||||
OPT_SET("Video/Dummy/Pattern")->SetBool(dlg.pattern);
|
OPT_SET("Video/Dummy/Pattern")->SetBool(dlg.pattern);
|
||||||
|
|
||||||
return DummyVideoProvider::MakeFilename(from_wx(dlg.fps), dlg.length, dlg.width, dlg.height, dlg.color, dlg.pattern);
|
return DummyVideoProvider::MakeFilename(dlg.fps, dlg.length, dlg.width, dlg.height, dlg.color, dlg.pattern);
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,14 +94,7 @@ void FontsCollectorThread(AssFile *subs, agi::fs::path const& destination, FcMod
|
||||||
collector->AddPendingEvent(ValueEvent<color_str_pair>(EVT_ADD_TEXT, -1, {colour, text.Clone()}));
|
collector->AddPendingEvent(ValueEvent<color_str_pair>(EVT_ADD_TEXT, -1, {colour, text.Clone()}));
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<agi::fs::path> paths;
|
auto paths = FontCollector(AppendText).GetFontPaths(subs);
|
||||||
try {
|
|
||||||
paths = FontCollector(AppendText).GetFontPaths(subs);
|
|
||||||
}
|
|
||||||
catch (agi::EnvironmentError const& err) {
|
|
||||||
AppendText(fmt_tl("* An error occurred when enumerating the used fonts: %s.\n", err.GetMessage()), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paths.empty()) {
|
if (paths.empty()) {
|
||||||
collector->AddPendingEvent(wxThreadEvent(EVT_COLLECTION_DONE));
|
collector->AddPendingEvent(wxThreadEvent(EVT_COLLECTION_DONE));
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
#include "dialog_progress.h"
|
#include "dialog_progress.h"
|
||||||
|
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "options.h"
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#include <libaegisub/dispatch.h>
|
#include <libaegisub/dispatch.h>
|
||||||
|
@ -122,7 +121,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
DialogProgress::DialogProgress(wxWindow *parent, wxString const& title_text, wxString const& message)
|
DialogProgress::DialogProgress(wxWindow *parent, wxString const& title_text, wxString const& message)
|
||||||
: wxDialog(parent, -1, title_text, wxDefaultPosition, wxDefaultSize, (OPT_GET("App/Dark Mode")->GetBool() ? wxBORDER_SIMPLE : wxBORDER_RAISED))
|
: wxDialog(parent, -1, title_text, wxDefaultPosition, wxDefaultSize, wxBORDER_RAISED)
|
||||||
, pulse_timer(GetEventHandler())
|
, pulse_timer(GetEventHandler())
|
||||||
{
|
{
|
||||||
title = new wxStaticText(this, -1, title_text, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE | wxST_NO_AUTORESIZE);
|
title = new wxStaticText(this, -1, title_text, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE | wxST_NO_AUTORESIZE);
|
||||||
|
@ -256,8 +255,6 @@ void DialogProgress::OnCancel(wxCommandEvent &) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogProgress::SetProgress(int target) {
|
void DialogProgress::SetProgress(int target) {
|
||||||
pulse_timer.Stop();
|
|
||||||
|
|
||||||
if (target == progress_target) return;
|
if (target == progress_target) return;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
|
|
@ -212,7 +212,7 @@ DialogStyleEditor::DialogStyleEditor(wxWindow *parent, AssStyle *style, agi::Con
|
||||||
auto ScaleX = num_text_ctrl(&work->scalex, 0.0, 10000.0, 1, 2);
|
auto ScaleX = num_text_ctrl(&work->scalex, 0.0, 10000.0, 1, 2);
|
||||||
auto ScaleY = num_text_ctrl(&work->scaley, 0.0, 10000.0, 1, 2);
|
auto ScaleY = num_text_ctrl(&work->scaley, 0.0, 10000.0, 1, 2);
|
||||||
auto Angle = num_text_ctrl(&work->angle, -360.0, 360.0, 1.0, 2);
|
auto Angle = num_text_ctrl(&work->angle, -360.0, 360.0, 1.0, 2);
|
||||||
auto Spacing = num_text_ctrl(&work->spacing, 0.0, 1000.0, 0.1, 3);
|
auto Spacing = num_text_ctrl(&work->spacing, -1000.0, 1000.0, 0.1, 3);
|
||||||
Encoding = new wxComboBox(this, -1, "", wxDefaultPosition, wxDefaultSize, encodingStrings, wxCB_READONLY);
|
Encoding = new wxComboBox(this, -1, "", wxDefaultPosition, wxDefaultSize, encodingStrings, wxCB_READONLY);
|
||||||
|
|
||||||
// Set control tooltips
|
// Set control tooltips
|
||||||
|
@ -258,7 +258,7 @@ DialogStyleEditor::DialogStyleEditor(wxWindow *parent, AssStyle *style, agi::Con
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) Encoding->Select(2);
|
if (!found) Encoding->Select(0);
|
||||||
|
|
||||||
// Style name sizer
|
// Style name sizer
|
||||||
NameSizer->Add(StyleName, 1, wxALL, 0);
|
NameSizer->Add(StyleName, 1, wxALL, 0);
|
||||||
|
@ -327,7 +327,7 @@ DialogStyleEditor::DialogStyleEditor(wxWindow *parent, AssStyle *style, agi::Con
|
||||||
// Preview
|
// Preview
|
||||||
auto previewButton = new ColourButton(this, wxSize(45, 16), false, OPT_GET("Colour/Style Editor/Background/Preview")->GetColor());
|
auto previewButton = new ColourButton(this, wxSize(45, 16), false, OPT_GET("Colour/Style Editor/Background/Preview")->GetColor());
|
||||||
PreviewText = new wxTextCtrl(this, -1, to_wx(OPT_GET("Tool/Style Editor/Preview Text")->GetString()));
|
PreviewText = new wxTextCtrl(this, -1, to_wx(OPT_GET("Tool/Style Editor/Preview Text")->GetString()));
|
||||||
SubsPreview = new SubtitlesPreview(this, wxSize(100, 60), (OPT_GET("App/Dark Mode")->GetBool() ? wxBORDER_SIMPLE : wxSUNKEN_BORDER), OPT_GET("Colour/Style Editor/Background/Preview")->GetColor());
|
SubsPreview = new SubtitlesPreview(this, wxSize(100, 60), wxSUNKEN_BORDER, OPT_GET("Colour/Style Editor/Background/Preview")->GetColor());
|
||||||
|
|
||||||
SubsPreview->SetToolTip(_("Preview of current style"));
|
SubsPreview->SetToolTip(_("Preview of current style"));
|
||||||
SubsPreview->SetStyle(*style);
|
SubsPreview->SetStyle(*style);
|
||||||
|
@ -474,10 +474,8 @@ void DialogStyleEditor::UpdateWorkStyle() {
|
||||||
|
|
||||||
work->font = from_wx(FontName->GetValue());
|
work->font = from_wx(FontName->GetValue());
|
||||||
|
|
||||||
wxString encoding_selection = Encoding->GetValue();
|
|
||||||
wxString encoding_num = encoding_selection.substr(0, 1) + encoding_selection.substr(1).BeforeFirst('-'); // Have to account for -1
|
|
||||||
long templ = 0;
|
long templ = 0;
|
||||||
encoding_num.ToLong(&templ);
|
Encoding->GetValue().BeforeFirst('-').ToLong(&templ);
|
||||||
work->encoding = templ;
|
work->encoding = templ;
|
||||||
|
|
||||||
work->borderstyle = OutlineType->IsChecked() ? 3 : 1;
|
work->borderstyle = OutlineType->IsChecked() ? 3 : 1;
|
||||||
|
|
|
@ -51,11 +51,6 @@
|
||||||
#include <wx/stattext.h>
|
#include <wx/stattext.h>
|
||||||
#include <wx/stc/stc.h>
|
#include <wx/stc/stc.h>
|
||||||
|
|
||||||
// Define macros for wxWidgets 3.1
|
|
||||||
#ifndef wxSTC_KEYMOD_SHIFT
|
|
||||||
#define wxSTC_KEYMOD_SHIFT wxSTC_SCMOD_SHIFT
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void add_hotkey(wxSizer *sizer, wxWindow *parent, const char *command, wxString const& text) {
|
static void add_hotkey(wxSizer *sizer, wxWindow *parent, const char *command, wxString const& text) {
|
||||||
sizer->Add(new wxStaticText(parent, -1, text));
|
sizer->Add(new wxStaticText(parent, -1, text));
|
||||||
sizer->Add(new wxStaticText(parent, -1, to_wx(hotkey::get_hotkey_str_first("Translation Assistant", command))));
|
sizer->Add(new wxStaticText(parent, -1, to_wx(hotkey::get_hotkey_str_first("Translation Assistant", command))));
|
||||||
|
@ -102,7 +97,7 @@ DialogTranslation::DialogTranslation(agi::Context *c)
|
||||||
translated_text->SetMarginWidth(1, 0);
|
translated_text->SetMarginWidth(1, 0);
|
||||||
translated_text->SetFocus();
|
translated_text->SetFocus();
|
||||||
translated_text->Bind(wxEVT_CHAR_HOOK, &DialogTranslation::OnKeyDown, this);
|
translated_text->Bind(wxEVT_CHAR_HOOK, &DialogTranslation::OnKeyDown, this);
|
||||||
translated_text->CmdKeyAssign(wxSTC_KEY_RETURN, wxSTC_KEYMOD_SHIFT, wxSTC_CMD_NEWLINE);
|
translated_text->CmdKeyAssign(wxSTC_KEY_RETURN, wxSTC_SCMOD_SHIFT, wxSTC_CMD_NEWLINE);
|
||||||
|
|
||||||
wxSizer *translated_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Translation"));
|
wxSizer *translated_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Translation"));
|
||||||
translated_box->Add(translated_text, 1, wxEXPAND, 0);
|
translated_box->Add(translated_text, 1, wxEXPAND, 0);
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <libaegisub/fs.h>
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
template<typename Container>
|
template<typename Container>
|
||||||
std::vector<std::string> GetClasses(Container const& c) {
|
std::vector<std::string> GetClasses(Container const& c) {
|
||||||
|
@ -52,21 +50,4 @@ auto GetSorted(Container const& c, std::string const& preferred) -> std::vector<
|
||||||
}
|
}
|
||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Container>
|
|
||||||
auto RearrangeWithPriority(Container &c, agi::fs::path const& filename) {
|
|
||||||
size_t end_of_hidden = 0;
|
|
||||||
for (size_t i = 0; i < c.size(); i++) {
|
|
||||||
auto provider = c[i];
|
|
||||||
if (provider->hidden) {
|
|
||||||
end_of_hidden = i;
|
|
||||||
} else {
|
|
||||||
if (provider->wants_to_open(filename)) {
|
|
||||||
c.erase(c.begin() + i);
|
|
||||||
c.insert(c.begin() + end_of_hidden + 1, provider);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,13 +79,16 @@ void FoldController::AddFold(AssDialogue& start, AssDialogue& end, bool collapse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FoldController::DoForAllFolds(std::function<void(AssDialogue&)> action) {
|
bool FoldController::DoForAllFolds(bool action(AssDialogue& line)) {
|
||||||
for (AssDialogue& line : context->ass->Events) {
|
for (AssDialogue& line : context->ass->Events) {
|
||||||
if (line.Fold.valid) {
|
if (line.Fold.valid) {
|
||||||
action(line);
|
bool result = action(line);
|
||||||
UpdateLineExtradata(line);
|
UpdateLineExtradata(line);
|
||||||
|
if (result)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FoldController::FixFoldsPreCommit(int type, const AssDialogue *single_line) {
|
void FoldController::FixFoldsPreCommit(int type, const AssDialogue *single_line) {
|
||||||
|
@ -95,22 +98,24 @@ void FoldController::FixFoldsPreCommit(int type, const AssDialogue *single_line)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For each line in lines, applies action() to the opening delimiter of the innermost fold containing this line.
|
// For each line in lines, applies action() to the opening delimiter of the innermost fold containing this line.
|
||||||
|
// Returns true as soon as any action() call returned true.
|
||||||
//
|
//
|
||||||
// In general, this can leave the folds in an inconsistent state, so unless action() is read-only this should always
|
// In general, this can leave the folds in an inconsistent state, so unless action() is read-only this should always
|
||||||
// be followed by a commit.
|
// be followed by a commit.
|
||||||
void FoldController::DoForFoldsAt(std::vector<AssDialogue *> const& lines, std::function<void(AssDialogue&)> action) {
|
bool FoldController::DoForFoldsAt(std::vector<AssDialogue *> const& lines, bool action(AssDialogue& line)) {
|
||||||
std::map<int, bool> visited;
|
|
||||||
for (AssDialogue *line : lines) {
|
for (AssDialogue *line : lines) {
|
||||||
if (line->Fold.parent != nullptr && !(line->Fold.valid && !line->Fold.side)) {
|
if (line->Fold.parent != nullptr && !(line->Fold.valid && !line->Fold.side)) {
|
||||||
line = line->Fold.parent;
|
line = line->Fold.parent;
|
||||||
}
|
}
|
||||||
if (visited.count(line->Row))
|
if (!line->Fold.visited) {
|
||||||
continue;
|
bool result = action(*line);
|
||||||
|
UpdateLineExtradata(*line);
|
||||||
action(*line);
|
if (result)
|
||||||
UpdateLineExtradata(*line);
|
return true;
|
||||||
visited[line->Row] = true;
|
}
|
||||||
|
line->Fold.visited = true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FoldController::UpdateFoldInfo() {
|
void FoldController::UpdateFoldInfo() {
|
||||||
|
@ -258,6 +263,7 @@ void FoldController::LinkFolds() {
|
||||||
line->Fold.parent = foldStack.empty() ? nullptr : foldStack.back();
|
line->Fold.parent = foldStack.empty() ? nullptr : foldStack.back();
|
||||||
line->Fold.nextVisible = nullptr;
|
line->Fold.nextVisible = nullptr;
|
||||||
line->Fold.visible = highestFolded > (int) foldStack.size();
|
line->Fold.visible = highestFolded > (int) foldStack.size();
|
||||||
|
line->Fold.visited = false;
|
||||||
line->Fold.visibleRow = visibleRow;
|
line->Fold.visibleRow = visibleRow;
|
||||||
|
|
||||||
if (line->Fold.visible) {
|
if (line->Fold.visible) {
|
||||||
|
@ -293,78 +299,56 @@ int FoldController::GetMaxDepth() {
|
||||||
return maxdepth;
|
return maxdepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FoldController::ActionHasFold(AssDialogue& line) { return line.Fold.valid; }
|
||||||
|
|
||||||
|
bool FoldController::ActionClearFold(AssDialogue& line) { line.Fold.extraExists = false; line.Fold.valid = false; return false; }
|
||||||
|
|
||||||
|
bool FoldController::ActionOpenFold(AssDialogue& line) { line.Fold.collapsed = false; return false; }
|
||||||
|
|
||||||
|
bool FoldController::ActionCloseFold(AssDialogue& line) { line.Fold.collapsed = true; return false; }
|
||||||
|
|
||||||
|
bool FoldController::ActionToggleFold(AssDialogue& line) { line.Fold.collapsed = !line.Fold.collapsed; return false; }
|
||||||
|
|
||||||
|
|
||||||
void FoldController::ClearAllFolds() {
|
void FoldController::ClearAllFolds() {
|
||||||
DoForAllFolds([&](AssDialogue &line) {
|
FoldController::DoForAllFolds(FoldController::ActionClearFold);
|
||||||
line.Fold.extraExists = false; line.Fold.valid = false;
|
|
||||||
});
|
|
||||||
context->ass->Commit(_("clear all folds"), AssFile::COMMIT_FOLD);
|
context->ass->Commit(_("clear all folds"), AssFile::COMMIT_FOLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FoldController::OpenAllFolds() {
|
void FoldController::OpenAllFolds() {
|
||||||
DoForAllFolds([&](AssDialogue &line) {
|
FoldController::DoForAllFolds(FoldController::ActionOpenFold);
|
||||||
line.Fold.collapsed = false;
|
|
||||||
});
|
|
||||||
context->ass->Commit(_("open all folds"), AssFile::COMMIT_FOLD);
|
context->ass->Commit(_("open all folds"), AssFile::COMMIT_FOLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FoldController::CloseAllFolds() {
|
void FoldController::CloseAllFolds() {
|
||||||
DoForAllFolds([&](AssDialogue &line) {
|
FoldController::DoForAllFolds(FoldController::ActionCloseFold);
|
||||||
line.Fold.collapsed = true;
|
|
||||||
});
|
|
||||||
context->ass->Commit(_("close all folds"), AssFile::COMMIT_FOLD);
|
context->ass->Commit(_("close all folds"), AssFile::COMMIT_FOLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FoldController::HasFolds() {
|
bool FoldController::HasFolds() {
|
||||||
bool hasfold = false;
|
return FoldController::DoForAllFolds(FoldController::ActionHasFold);
|
||||||
DoForAllFolds([&](AssDialogue &line) {
|
|
||||||
hasfold = hasfold || line.Fold.valid;
|
|
||||||
});
|
|
||||||
return hasfold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FoldController::ClearFoldsAt(std::vector<AssDialogue *> const& lines) {
|
void FoldController::ClearFoldsAt(std::vector<AssDialogue *> const& lines) {
|
||||||
DoForFoldsAt(lines, [&](AssDialogue &line) {
|
FoldController::DoForFoldsAt(lines, FoldController::ActionClearFold);
|
||||||
line.Fold.extraExists = false; line.Fold.valid = false;
|
|
||||||
if (line.Fold.counterpart) {
|
|
||||||
line.Fold.counterpart->Fold.extraExists = false;
|
|
||||||
line.Fold.counterpart->Fold.valid = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
context->ass->Commit(_("clear folds"), AssFile::COMMIT_FOLD);
|
context->ass->Commit(_("clear folds"), AssFile::COMMIT_FOLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FoldController::OpenFoldsAt(std::vector<AssDialogue *> const& lines) {
|
void FoldController::OpenFoldsAt(std::vector<AssDialogue *> const& lines) {
|
||||||
DoForFoldsAt(lines, [&](AssDialogue &line) {
|
FoldController::DoForFoldsAt(lines, FoldController::ActionOpenFold);
|
||||||
line.Fold.collapsed = false;
|
|
||||||
if (line.Fold.counterpart)
|
|
||||||
line.Fold.counterpart->Fold.collapsed = line.Fold.collapsed;
|
|
||||||
});
|
|
||||||
context->ass->Commit(_("open folds"), AssFile::COMMIT_FOLD);
|
context->ass->Commit(_("open folds"), AssFile::COMMIT_FOLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FoldController::CloseFoldsAt(std::vector<AssDialogue *> const& lines) {
|
void FoldController::CloseFoldsAt(std::vector<AssDialogue *> const& lines) {
|
||||||
DoForFoldsAt(lines, [&](AssDialogue &line) {
|
FoldController::DoForFoldsAt(lines, FoldController::ActionCloseFold);
|
||||||
line.Fold.collapsed = true;
|
|
||||||
if (line.Fold.counterpart)
|
|
||||||
line.Fold.counterpart->Fold.collapsed = line.Fold.collapsed;
|
|
||||||
});
|
|
||||||
context->ass->Commit(_("close folds"), AssFile::COMMIT_FOLD);
|
context->ass->Commit(_("close folds"), AssFile::COMMIT_FOLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FoldController::ToggleFoldsAt(std::vector<AssDialogue *> const& lines) {
|
void FoldController::ToggleFoldsAt(std::vector<AssDialogue *> const& lines) {
|
||||||
DoForFoldsAt(lines, [&](AssDialogue &line) {
|
FoldController::DoForFoldsAt(lines, FoldController::ActionToggleFold);
|
||||||
line.Fold.collapsed = !line.Fold.collapsed;
|
|
||||||
if (line.Fold.counterpart)
|
|
||||||
line.Fold.counterpart->Fold.collapsed = line.Fold.collapsed;
|
|
||||||
});
|
|
||||||
context->ass->Commit(_("toggle folds"), AssFile::COMMIT_FOLD);
|
context->ass->Commit(_("toggle folds"), AssFile::COMMIT_FOLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FoldController::AreFoldsAt(std::vector<AssDialogue *> const& lines) {
|
bool FoldController::AreFoldsAt(std::vector<AssDialogue *> const& lines) {
|
||||||
bool hasfold = false;
|
return FoldController::DoForFoldsAt(lines, FoldController::ActionHasFold);
|
||||||
DoForFoldsAt(lines, [&](AssDialogue &line) {
|
|
||||||
hasfold = hasfold || line.Fold.valid;
|
|
||||||
});
|
|
||||||
return hasfold;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,9 @@ class FoldInfo {
|
||||||
/// False if a fold is started here, true otherwise.
|
/// False if a fold is started here, true otherwise.
|
||||||
bool side = false;
|
bool side = false;
|
||||||
|
|
||||||
|
|
||||||
|
// Used in DoForFoldsAt to ensure each line is visited only once
|
||||||
|
bool visited = false;
|
||||||
/// Whether the line is currently visible
|
/// Whether the line is currently visible
|
||||||
bool visible = true;
|
bool visible = true;
|
||||||
|
|
||||||
|
@ -103,12 +106,24 @@ class FoldController {
|
||||||
|
|
||||||
void RawAddFold(AssDialogue& start, AssDialogue& end, bool collapsed);
|
void RawAddFold(AssDialogue& start, AssDialogue& end, bool collapsed);
|
||||||
|
|
||||||
void DoForFoldsAt(std::vector<AssDialogue *> const& lines, std::function<void(AssDialogue&)> action);
|
bool DoForFoldsAt(std::vector<AssDialogue *> const& lines, bool action(AssDialogue& line));
|
||||||
|
|
||||||
void DoForAllFolds(std::function<void(AssDialogue&)> action);
|
bool DoForAllFolds(bool action(AssDialogue& line));
|
||||||
|
|
||||||
void FixFoldsPreCommit(int type, const AssDialogue *single_line);
|
void FixFoldsPreCommit(int type, const AssDialogue *single_line);
|
||||||
|
|
||||||
|
// These are used for the DoForAllFolds action and should not be used as ordinary getters/setters
|
||||||
|
|
||||||
|
static bool ActionHasFold(AssDialogue& line);
|
||||||
|
|
||||||
|
static bool ActionClearFold(AssDialogue& line);
|
||||||
|
|
||||||
|
static bool ActionOpenFold(AssDialogue& line);
|
||||||
|
|
||||||
|
static bool ActionCloseFold(AssDialogue& line);
|
||||||
|
|
||||||
|
static bool ActionToggleFold(AssDialogue& line);
|
||||||
|
|
||||||
/// Updates the line's extradata entry from the values in FoldInfo. Used after actions like toggling folds.
|
/// Updates the line's extradata entry from the values in FoldInfo. Used after actions like toggling folds.
|
||||||
void UpdateLineExtradata(AssDialogue& line);
|
void UpdateLineExtradata(AssDialogue& line);
|
||||||
|
|
||||||
|
|
|
@ -139,8 +139,6 @@ void FontCollector::ProcessDialogueLine(const AssDialogue *line, int index) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AssBlockType::DRAWING:
|
case AssBlockType::DRAWING:
|
||||||
used_styles[style].drawing = true;
|
|
||||||
break;
|
|
||||||
case AssBlockType::COMMENT:
|
case AssBlockType::COMMENT:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -148,11 +146,7 @@ void FontCollector::ProcessDialogueLine(const AssDialogue *line, int index) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FontCollector::ProcessChunk(std::pair<StyleInfo, UsageData> const& style) {
|
void FontCollector::ProcessChunk(std::pair<StyleInfo, UsageData> const& style) {
|
||||||
if (style.second.chars.empty() && !style.second.drawing) return;
|
if (style.second.chars.empty()) return;
|
||||||
|
|
||||||
if (style.second.chars.empty() && style.second.drawing) {
|
|
||||||
status_callback(fmt_tl("Font '%s' is used in a drawing, but not in any text.\n", style.first.facename), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto res = lister.GetFontPaths(style.first.facename, style.first.bold, style.first.italic, style.second.chars);
|
auto res = lister.GetFontPaths(style.first.facename, style.first.bold, style.first.italic, style.second.chars);
|
||||||
|
|
||||||
|
|
|
@ -41,17 +41,17 @@ struct CollectionResult {
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <dwrite.h>
|
|
||||||
class GdiFontFileLister {
|
class GdiFontFileLister {
|
||||||
agi::scoped_holder<HDC> dc_sh;
|
std::unordered_multimap<uint32_t, agi::fs::path> index;
|
||||||
agi::scoped_holder<IDWriteFactory*> dwrite_factory_sh;
|
agi::scoped_holder<HDC> dc;
|
||||||
agi::scoped_holder<IDWriteFontCollection*> font_collection_sh;
|
std::string buffer;
|
||||||
agi::scoped_holder<IDWriteGdiInterop*> gdi_interop_sh;
|
|
||||||
|
bool ProcessLogFont(LOGFONTW const& expected, LOGFONTW const& actual, std::vector<int> const& characters);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// @throws agi::EnvironmentError if an error occurs during construction.
|
/// @param cb Callback for status logging
|
||||||
GdiFontFileLister(FontCollectorStatusCallback &);
|
GdiFontFileLister(FontCollectorStatusCallback &cb);
|
||||||
|
|
||||||
/// @brief Get the path to the font with the given styles
|
/// @brief Get the path to the font with the given styles
|
||||||
/// @param facename Name of font face
|
/// @param facename Name of font face
|
||||||
|
@ -127,7 +127,6 @@ class FontCollector {
|
||||||
/// Data about where each style is used
|
/// Data about where each style is used
|
||||||
struct UsageData {
|
struct UsageData {
|
||||||
std::vector<int> chars; ///< Characters used in this style which glyphs will be needed for
|
std::vector<int> chars; ///< Characters used in this style which glyphs will be needed for
|
||||||
bool drawing = false; ///< Whether this style is used for a drawing
|
|
||||||
std::vector<int> lines; ///< Lines on which this style is used via overrides
|
std::vector<int> lines; ///< Lines on which this style is used via overrides
|
||||||
std::vector<std::string> styles; ///< ASS styles which use this style
|
std::vector<std::string> styles; ///< ASS styles which use this style
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,199 +16,284 @@
|
||||||
|
|
||||||
#include "font_file_lister.h"
|
#include "font_file_lister.h"
|
||||||
|
|
||||||
|
#include "compat.h"
|
||||||
|
|
||||||
#include <libaegisub/charset_conv_win.h>
|
#include <libaegisub/charset_conv_win.h>
|
||||||
|
#include <libaegisub/fs.h>
|
||||||
|
#include <libaegisub/io.h>
|
||||||
|
#include <libaegisub/log.h>
|
||||||
|
|
||||||
#include <dwrite.h>
|
#include <ShlObj.h>
|
||||||
#include <wchar.h>
|
#include <boost/scope_exit.hpp>
|
||||||
#include <windowsx.h>
|
#include <unicode/utf16.h>
|
||||||
|
#include <Usp10.h>
|
||||||
|
|
||||||
#ifdef HAVE_DWRITE_3
|
static void read_fonts_from_key(HKEY hkey, agi::fs::path font_dir, std::vector<agi::fs::path> &files) {
|
||||||
#include <dwrite_3.h>
|
static const auto fonts_key_name = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
|
||||||
#endif
|
|
||||||
|
HKEY key;
|
||||||
|
auto ret = RegOpenKeyExW(hkey, fonts_key_name, 0, KEY_QUERY_VALUE, &key);
|
||||||
|
if (ret != ERROR_SUCCESS) return;
|
||||||
|
BOOST_SCOPE_EXIT_ALL(=) { RegCloseKey(key); };
|
||||||
|
|
||||||
/// @brief Normalize the case of a file path.
|
DWORD name_buf_size = SHRT_MAX;
|
||||||
/// @param path The path to be normalized. It can be a directory or a file.
|
DWORD data_buf_size = MAX_PATH;
|
||||||
/// @return A string representing the normalized path.
|
|
||||||
/// If the path normalization fails due to file handling errors or other issues,
|
|
||||||
/// an empty string is returned.
|
|
||||||
/// @example For "C:\WINDOWS\FONTS\ARIAL.TTF", it would return "C:\Windows\Fonts\arial.ttf"
|
|
||||||
std::wstring normalizeFilePathCase(const std::wstring path) {
|
|
||||||
/* FILE_FLAG_BACKUP_SEMANTICS is required to open a directory */
|
|
||||||
HANDLE hfile = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
|
||||||
if (hfile == INVALID_HANDLE_VALUE)
|
|
||||||
return L"";
|
|
||||||
agi::scoped_holder<HANDLE> hfile_sh(hfile, [](HANDLE hfile) { CloseHandle(hfile); });
|
|
||||||
|
|
||||||
DWORD normalized_path_length = GetFinalPathNameByHandle(hfile_sh, nullptr, 0, FILE_NAME_NORMALIZED);
|
auto font_name = new wchar_t[name_buf_size];
|
||||||
if (!normalized_path_length)
|
auto font_filename = new wchar_t[data_buf_size];
|
||||||
return L"";
|
|
||||||
|
|
||||||
agi::scoped_holder<WCHAR*> normalized_path_sh(new WCHAR[normalized_path_length + 1], [](WCHAR* p) { delete[] p; });
|
for (DWORD i = 0;; ++i) {
|
||||||
if (!GetFinalPathNameByHandle(hfile_sh, normalized_path_sh, normalized_path_length + 1, FILE_NAME_NORMALIZED))
|
retry:
|
||||||
return L"";
|
DWORD name_len = name_buf_size;
|
||||||
|
DWORD data_len = data_buf_size;
|
||||||
|
|
||||||
std::wstring normalized_path(normalized_path_sh);
|
ret = RegEnumValueW(key, i, font_name, &name_len, NULL, NULL, reinterpret_cast<BYTE*>(font_filename), &data_len);
|
||||||
|
if (ret == ERROR_MORE_DATA) {
|
||||||
|
data_buf_size = data_len;
|
||||||
|
delete font_filename;
|
||||||
|
font_filename = new wchar_t[data_buf_size];
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
if (ret == ERROR_NO_MORE_ITEMS) break;
|
||||||
|
if (ret != ERROR_SUCCESS) continue;
|
||||||
|
|
||||||
// GetFinalPathNameByHandle return path into ``device path`` form. Ex: "\\?\C:\Windows\Fonts\ariali.ttf"
|
agi::fs::path font_path(font_filename);
|
||||||
// We need to convert it to ``fully qualified DOS Path``. Ex: "C:\Windows\Fonts\ariali.ttf"
|
if (!agi::fs::FileExists(font_path))
|
||||||
// There isn't any public API that remove the prefix (there is RtlNtPathNameToDosPathName, but it is really hacky to use it)
|
// Doesn't make a ton of sense to do this with user fonts, but they seem to be stored as full paths anyway
|
||||||
// See: https://stackoverflow.com/questions/31439011/getfinalpathnamebyhandle-result-without-prepended
|
font_path = font_dir / font_path;
|
||||||
// Even CPython remove the prefix manually: https://github.com/python/cpython/blob/963904335e579bfe39101adf3fd6a0cf705975ff/Lib/ntpath.py#L733-L793
|
if (agi::fs::FileExists(font_path)) // The path might simply be invalid
|
||||||
// Gecko: https://github.com/mozilla/gecko-dev/blob/6032a565e3be7dcdd01e4fe26791c84f9222a2e0/widget/windows/WinUtils.cpp#L1577-L1584
|
files.push_back(font_path);
|
||||||
if (normalized_path.compare(0, 7, L"\\\\?\\UNC") == 0)
|
}
|
||||||
normalized_path.erase(2, 6);
|
|
||||||
else if (normalized_path.compare(0, 4, L"\\\\?\\") == 0)
|
|
||||||
normalized_path.erase(0, 4);
|
|
||||||
|
|
||||||
return normalized_path;
|
delete font_name;
|
||||||
|
delete font_filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
GdiFontFileLister::GdiFontFileLister(FontCollectorStatusCallback &)
|
namespace {
|
||||||
: dwrite_factory_sh(nullptr, [](IDWriteFactory* p) { p->Release(); })
|
uint32_t murmur3(const char *data, uint32_t len) {
|
||||||
, font_collection_sh(nullptr, [](IDWriteFontCollection* p) { p->Release(); })
|
static const uint32_t c1 = 0xcc9e2d51;
|
||||||
, dc_sh(nullptr, [](HDC dc) { DeleteDC(dc); })
|
static const uint32_t c2 = 0x1b873593;
|
||||||
, gdi_interop_sh(nullptr, [](IDWriteGdiInterop* p) { p->Release(); })
|
static const uint32_t r1 = 15;
|
||||||
|
static const uint32_t r2 = 13;
|
||||||
|
static const uint32_t m = 5;
|
||||||
|
static const uint32_t n = 0xe6546b64;
|
||||||
|
|
||||||
|
uint32_t hash = 0;
|
||||||
|
|
||||||
|
const int nblocks = len / 4;
|
||||||
|
auto blocks = reinterpret_cast<const uint32_t *>(data);
|
||||||
|
for (uint32_t i = 0; i * 4 < len; ++i) {
|
||||||
|
uint32_t k = blocks[i];
|
||||||
|
k *= c1;
|
||||||
|
k = _rotl(k, r1);
|
||||||
|
k *= c2;
|
||||||
|
|
||||||
|
hash ^= k;
|
||||||
|
hash = _rotl(hash, r2) * m + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
hash ^= len;
|
||||||
|
hash ^= hash >> 16;
|
||||||
|
hash *= 0x85ebca6b;
|
||||||
|
hash ^= hash >> 13;
|
||||||
|
hash *= 0xc2b2ae35;
|
||||||
|
hash ^= hash >> 16;
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<agi::fs::path> get_installed_fonts() {
|
||||||
|
std::vector<agi::fs::path> files;
|
||||||
|
|
||||||
|
wchar_t fdir[MAX_PATH];
|
||||||
|
SHGetFolderPathW(NULL, CSIDL_FONTS, NULL, 0, fdir);
|
||||||
|
agi::fs::path font_dir(fdir);
|
||||||
|
|
||||||
|
// System fonts
|
||||||
|
read_fonts_from_key(HKEY_LOCAL_MACHINE, font_dir, files);
|
||||||
|
|
||||||
|
// User fonts
|
||||||
|
read_fonts_from_key(HKEY_CURRENT_USER, font_dir, files);
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
using font_index = std::unordered_multimap<uint32_t, agi::fs::path>;
|
||||||
|
|
||||||
|
font_index index_fonts(FontCollectorStatusCallback &cb) {
|
||||||
|
font_index hash_to_path;
|
||||||
|
auto fonts = get_installed_fonts();
|
||||||
|
std::unique_ptr<char[]> buffer(new char[1024]);
|
||||||
|
for (auto const& path : fonts) {
|
||||||
|
try {
|
||||||
|
auto stream = agi::io::Open(path, true);
|
||||||
|
stream->read(&buffer[0], 1024);
|
||||||
|
auto hash = murmur3(&buffer[0], stream->tellg());
|
||||||
|
hash_to_path.emplace(hash, path);
|
||||||
|
}
|
||||||
|
catch (agi::Exception const& e) {
|
||||||
|
cb(to_wx(e.GetMessage() + "\n"), 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hash_to_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_font_data(std::string& buffer, HDC dc) {
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
|
// For ttc files we have to ask for the "ttcf" table to get the complete file
|
||||||
|
DWORD ttcf = 0x66637474;
|
||||||
|
auto size = GetFontData(dc, ttcf, 0, nullptr, 0);
|
||||||
|
if (size == GDI_ERROR) {
|
||||||
|
ttcf = 0;
|
||||||
|
size = GetFontData(dc, 0, 0, nullptr, 0);
|
||||||
|
}
|
||||||
|
if (size == GDI_ERROR || size == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
buffer.resize(size);
|
||||||
|
GetFontData(dc, ttcf, 0, &buffer[0], size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GdiFontFileLister::GdiFontFileLister(FontCollectorStatusCallback &cb)
|
||||||
|
: dc(CreateCompatibleDC(nullptr), [](HDC dc) { DeleteDC(dc); })
|
||||||
{
|
{
|
||||||
IDWriteFactory* dwrite_factory;
|
cb(_("Updating font cache\n"), 0);
|
||||||
if (FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&dwrite_factory))))
|
index = index_fonts(cb);
|
||||||
throw agi::EnvironmentError("Failed to initialize the DirectWrite Factory");
|
|
||||||
dwrite_factory_sh = dwrite_factory;
|
|
||||||
|
|
||||||
IDWriteFontCollection* font_collection;
|
|
||||||
if (FAILED(dwrite_factory_sh->GetSystemFontCollection(&font_collection, true)))
|
|
||||||
throw agi::EnvironmentError("Failed to initialize the system font collection");
|
|
||||||
font_collection_sh = font_collection;
|
|
||||||
|
|
||||||
HDC dc = CreateCompatibleDC(nullptr);
|
|
||||||
if (dc == nullptr)
|
|
||||||
throw agi::EnvironmentError("Failed to initialize the HDC");
|
|
||||||
dc_sh = dc;
|
|
||||||
|
|
||||||
IDWriteGdiInterop* gdi_interop;
|
|
||||||
if (FAILED(dwrite_factory_sh->GetGdiInterop(&gdi_interop)))
|
|
||||||
throw agi::EnvironmentError("Failed to initialize the Gdi Interop");
|
|
||||||
gdi_interop_sh = gdi_interop;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionResult GdiFontFileLister::GetFontPaths(std::string const& facename, int bold, bool italic, std::vector<int> const& characters) {
|
CollectionResult GdiFontFileLister::GetFontPaths(std::string const& facename, int bold, bool italic, std::vector<int> const& characters) {
|
||||||
CollectionResult ret;
|
CollectionResult ret;
|
||||||
|
|
||||||
int weight = bold == 0 ? 400 :
|
|
||||||
bold == 1 ? 700 :
|
|
||||||
bold;
|
|
||||||
|
|
||||||
// From VSFilter
|
|
||||||
// - https://sourceforge.net/p/guliverkli2/code/HEAD/tree/src/subtitles/RTS.cpp#l45
|
|
||||||
// - https://sourceforge.net/p/guliverkli2/code/HEAD/tree/src/subtitles/STS.cpp#l2992
|
|
||||||
LOGFONTW lf{};
|
LOGFONTW lf{};
|
||||||
lf.lfCharSet = DEFAULT_CHARSET; // FIXME: Note that this currently ignores the font encoding specified in the ass file.
|
lf.lfCharSet = DEFAULT_CHARSET;
|
||||||
wcsncpy_s(lf.lfFaceName, LF_FACESIZE, agi::charset::ConvertW(facename).c_str(), _TRUNCATE);
|
wcsncpy(lf.lfFaceName, agi::charset::ConvertW(facename).c_str(), LF_FACESIZE);
|
||||||
lf.lfItalic = italic ? -1 : 0;
|
lf.lfItalic = italic ? -1 : 0;
|
||||||
lf.lfWeight = weight;
|
lf.lfWeight = bold == 0 ? 400 :
|
||||||
lf.lfOutPrecision = OUT_TT_PRECIS;
|
bold == 1 ? 700 :
|
||||||
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
bold;
|
||||||
lf.lfQuality = ANTIALIASED_QUALITY;
|
|
||||||
lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
|
|
||||||
|
|
||||||
agi::scoped_holder<HFONT> hfont_sh(CreateFontIndirect(&lf), [](HFONT p) { DeleteObject(p); });
|
// Gather all of the styles for the given family name
|
||||||
if (hfont_sh == nullptr)
|
std::vector<LOGFONTW> matches;
|
||||||
|
using type = decltype(matches);
|
||||||
|
EnumFontFamiliesEx(dc, &lf, [](const LOGFONT *lf, const TEXTMETRIC *, DWORD, LPARAM lParam) -> int {
|
||||||
|
reinterpret_cast<type*>(lParam)->push_back(*lf);
|
||||||
|
return 1;
|
||||||
|
}, (LPARAM)&matches, 0);
|
||||||
|
|
||||||
|
if (matches.empty())
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
SelectFont(dc_sh, hfont_sh);
|
// If the user asked for a non-regular style, verify that it actually exists
|
||||||
|
if (italic || bold) {
|
||||||
|
bool has_bold = false;
|
||||||
|
bool has_italic = false;
|
||||||
|
bool has_bold_italic = false;
|
||||||
|
|
||||||
std::wstring selected_name(LF_FACESIZE - 1, L'\0');
|
auto is_italic = [&](LOGFONTW const& lf) {
|
||||||
// FIXME: This will override the string's terminator, which is not technically correct.
|
return !italic || lf.lfItalic;
|
||||||
// After switching to C++20 this should use .data().
|
};
|
||||||
if (!GetTextFaceW(dc_sh, LF_FACESIZE, &selected_name[0]))
|
auto is_bold = [&](LOGFONTW const& lf) {
|
||||||
|
return !bold
|
||||||
|
|| (bold == 1 && lf.lfWeight >= 700)
|
||||||
|
|| (bold > 1 && lf.lfWeight > bold);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto const& match : matches) {
|
||||||
|
has_bold = has_bold || is_bold(match);
|
||||||
|
has_italic = has_italic || is_italic(match);
|
||||||
|
has_bold_italic = has_bold_italic || (is_bold(match) && is_italic(match));
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.fake_italic = !has_italic;
|
||||||
|
ret.fake_bold = (italic && has_italic ? !has_bold_italic : !has_bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the family name supplied by EnumFontFamiliesEx as it may be a localized version
|
||||||
|
memcpy(lf.lfFaceName, matches[0].lfFaceName, LF_FACESIZE);
|
||||||
|
|
||||||
|
// Open the font and get the data for it to look up in the index
|
||||||
|
auto hfont = CreateFontIndirectW(&lf);
|
||||||
|
SelectObject(dc, hfont);
|
||||||
|
BOOST_SCOPE_EXIT_ALL(=) {
|
||||||
|
SelectObject(dc, nullptr);
|
||||||
|
DeleteObject(hfont);
|
||||||
|
};
|
||||||
|
|
||||||
|
get_font_data(buffer, dc);
|
||||||
|
|
||||||
|
auto range = index.equal_range(murmur3(buffer.c_str(), std::min<size_t>(buffer.size(), 1024U)));
|
||||||
|
if (range.first == range.second)
|
||||||
|
return ret; // could instead write to a temp dir
|
||||||
|
|
||||||
|
// Compare the full files for each of the fonts with the same prefix
|
||||||
|
std::unique_ptr<char[]> file_buffer(new char[buffer.size()]);
|
||||||
|
for (auto it = range.first; it != range.second; ++it) {
|
||||||
|
auto stream = agi::io::Open(it->second, true);
|
||||||
|
stream->read(&file_buffer[0], buffer.size());
|
||||||
|
if ((size_t)stream->tellg() != buffer.size())
|
||||||
|
continue;
|
||||||
|
if (memcmp(&file_buffer[0], &buffer[0], buffer.size()) == 0) {
|
||||||
|
ret.paths.push_back(it->second);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No fonts actually matched
|
||||||
|
if (ret.paths.empty())
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
// If the selected_name is different then the lf.lfFaceName,
|
// Convert the characters to a utf-16 string
|
||||||
// it means that the requested font doesn't exist.
|
std::wstring utf16characters;
|
||||||
if (_wcsnicmp(&selected_name[0], lf.lfFaceName, LF_FACESIZE))
|
utf16characters.reserve(characters.size());
|
||||||
return ret;
|
for (int chr : characters) {
|
||||||
|
if (U16_LENGTH(chr) == 1)
|
||||||
|
utf16characters.push_back(static_cast<wchar_t>(chr));
|
||||||
|
else {
|
||||||
|
utf16characters.push_back(U16_LEAD(chr));
|
||||||
|
utf16characters.push_back(U16_TRAIL(chr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IDWriteFontFace* font_face;
|
SCRIPT_CACHE cache = nullptr;
|
||||||
if (FAILED(gdi_interop_sh->CreateFontFaceFromHdc(dc_sh, &font_face)))
|
std::unique_ptr<WORD[]> indices(new WORD[utf16characters.size()]);
|
||||||
return ret;
|
|
||||||
agi::scoped_holder<IDWriteFontFace*> font_face_sh(font_face, [](IDWriteFontFace* p) { p->Release(); });
|
|
||||||
|
|
||||||
ret.fake_italic = font_face_sh->GetSimulations() & DWRITE_FONT_SIMULATIONS_OBLIQUE;
|
// First try to check glyph coverage with Uniscribe, since it
|
||||||
ret.fake_bold = font_face_sh->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD;
|
// handles non-BMP unicode characters
|
||||||
|
auto hr = ScriptGetCMap(dc, &cache, utf16characters.data(),
|
||||||
|
utf16characters.size(), 0, indices.get());
|
||||||
|
|
||||||
bool is_query_font_face_3_succeeded = false;
|
// Uniscribe doesn't like some types of fonts, so fall back to GDI
|
||||||
#ifdef HAVE_DWRITE_3
|
if (hr == E_HANDLE) {
|
||||||
// Fonts added via the AddFontResource API are not included in the IDWriteFontCollection.
|
GetGlyphIndicesW(dc, utf16characters.data(), utf16characters.size(),
|
||||||
// This omission causes GetFontFromFontFace to fail.
|
indices.get(), GGI_MARK_NONEXISTING_GLYPHS);
|
||||||
// This issue is unavoidable on Windows 8 or lower.
|
for (size_t i = 0; i < utf16characters.size(); ++i) {
|
||||||
// However, on Windows 10 or higher, we address this by querying IDWriteFontFace to IDWriteFontFace3.
|
if (U16_IS_SURROGATE(utf16characters[i]))
|
||||||
// From this new instance, we can verify font character(s) availability.
|
continue;
|
||||||
|
if (indices[i] == SHRT_MAX)
|
||||||
IDWriteFontFace3* font_face_3;
|
ret.missing += utf16characters[i];
|
||||||
if (SUCCEEDED(font_face_sh->QueryInterface(__uuidof(IDWriteFontFace3), (void**)&font_face_3))) {
|
}
|
||||||
agi::scoped_holder<IDWriteFontFace3*> font_face_3_sh(font_face_3, [](IDWriteFontFace3* p) { p->Release(); });
|
}
|
||||||
is_query_font_face_3_succeeded = true;
|
else if (hr == S_FALSE) {
|
||||||
|
for (size_t i = 0; i < utf16characters.size(); ++i) {
|
||||||
for (int character : characters) {
|
// Uniscribe doesn't report glyph indexes for non-BMP characters,
|
||||||
if (!font_face_3_sh->HasCharacter((UINT32)character)) {
|
// so we have to call ScriptGetCMap on each individual pair to
|
||||||
ret.missing += character;
|
// determine if it's the missing one
|
||||||
|
if (U16_IS_LEAD(utf16characters[i])) {
|
||||||
|
hr = ScriptGetCMap(dc, &cache, &utf16characters[i], 2, 0, &indices[i]);
|
||||||
|
if (hr == S_FALSE) {
|
||||||
|
ret.missing += utf16characters[i];
|
||||||
|
ret.missing += utf16characters[i + 1];
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
else if (indices[i] == 0) {
|
||||||
|
ret.missing += utf16characters[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
ScriptFreeCache(&cache);
|
||||||
|
|
||||||
if (!is_query_font_face_3_succeeded) {
|
|
||||||
IDWriteFont* font;
|
|
||||||
if (FAILED(font_collection_sh->GetFontFromFontFace(font_face_sh, &font)))
|
|
||||||
return ret;
|
|
||||||
agi::scoped_holder<IDWriteFont*> font_sh(font, [](IDWriteFont* p) { p->Release(); });
|
|
||||||
|
|
||||||
BOOL exists;
|
|
||||||
HRESULT hr;
|
|
||||||
for (int character : characters) {
|
|
||||||
hr = font_sh->HasCharacter((UINT32)character, &exists);
|
|
||||||
if (FAILED(hr) || !exists)
|
|
||||||
ret.missing += character;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UINT32 file_count = 1;
|
|
||||||
IDWriteFontFile* font_file;
|
|
||||||
// DirectWrite only supports one file per face
|
|
||||||
if (FAILED(font_face_sh->GetFiles(&file_count, &font_file)))
|
|
||||||
return ret;
|
|
||||||
agi::scoped_holder<IDWriteFontFile*> font_file_sh(font_file, [](IDWriteFontFile* p) { p->Release(); });
|
|
||||||
|
|
||||||
IDWriteFontFileLoader* loader;
|
|
||||||
if (FAILED(font_file_sh->GetLoader(&loader)))
|
|
||||||
return ret;
|
|
||||||
agi::scoped_holder<IDWriteFontFileLoader*> loader_sh(loader, [](IDWriteFontFileLoader* p) { p->Release(); });
|
|
||||||
|
|
||||||
IDWriteLocalFontFileLoader* local_loader;
|
|
||||||
if (FAILED(loader_sh->QueryInterface(__uuidof(IDWriteLocalFontFileLoader), (void**)&local_loader)))
|
|
||||||
return ret;
|
|
||||||
agi::scoped_holder<IDWriteLocalFontFileLoader*> local_loader_sh(local_loader, [](IDWriteLocalFontFileLoader* p) { p->Release(); });
|
|
||||||
|
|
||||||
LPCVOID font_file_reference_key;
|
|
||||||
UINT32 font_file_reference_key_size;
|
|
||||||
if (FAILED(font_file_sh->GetReferenceKey(&font_file_reference_key, &font_file_reference_key_size)))
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
UINT32 path_length;
|
|
||||||
if (FAILED(local_loader_sh->GetFilePathLengthFromKey(font_file_reference_key, font_file_reference_key_size, &path_length)))
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
std::wstring path(path_length, L'\0');
|
|
||||||
// FIXME: This will override the string's terminator, which is not technically correct.
|
|
||||||
// After switching to C++20 this should use .data().
|
|
||||||
if (FAILED(local_loader_sh->GetFilePathFromKey(font_file_reference_key, font_file_reference_key_size, &path[0], path_length + 1)))
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
// DirectWrite always return the file path in upper case. Ex: "C:\WINDOWS\FONTS\ARIAL.TTF"
|
|
||||||
std::wstring normalized_path = normalizeFilePathCase(path);
|
|
||||||
if (normalized_path.empty())
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
ret.paths.push_back(agi::fs::path(normalized_path));
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,7 +128,7 @@ FrameMain::FrameMain()
|
||||||
EnableToolBar(*OPT_GET("App/Show Toolbar"));
|
EnableToolBar(*OPT_GET("App/Show Toolbar"));
|
||||||
|
|
||||||
StartupLog("Initialize menu bar");
|
StartupLog("Initialize menu bar");
|
||||||
menu::GetMenuBar("main", this, (wxID_HIGHEST + 1) + 10000, context.get());
|
menu::GetMenuBar("main", this, context.get());
|
||||||
|
|
||||||
StartupLog("Create status bar");
|
StartupLog("Create status bar");
|
||||||
CreateStatusBar(2);
|
CreateStatusBar(2);
|
||||||
|
|
|
@ -150,7 +150,9 @@ struct GridColumnFolds final : GridColumn {
|
||||||
bool OnMouseEvent(AssDialogue *d, agi::Context *c, wxMouseEvent &event) const override {
|
bool OnMouseEvent(AssDialogue *d, agi::Context *c, wxMouseEvent &event) const override {
|
||||||
if ((event.LeftDown() || event.LeftDClick()) && !event.ShiftDown() && !event.CmdDown() && !event.AltDown()) {
|
if ((event.LeftDown() || event.LeftDClick()) && !event.ShiftDown() && !event.CmdDown() && !event.AltDown()) {
|
||||||
if (d->Fold.hasFold() && !d->Fold.isEnd()) {
|
if (d->Fold.hasFold() && !d->Fold.isEnd()) {
|
||||||
c->foldController->ToggleFoldsAt({d});
|
std::vector<AssDialogue *> lines;
|
||||||
|
lines.push_back(d);
|
||||||
|
c->foldController->ToggleFoldsAt(lines);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,15 +206,10 @@ struct GridColumnStartTime final : GridColumnTime {
|
||||||
}
|
}
|
||||||
|
|
||||||
int Width(const agi::Context *c, WidthHelper &helper) const override {
|
int Width(const agi::Context *c, WidthHelper &helper) const override {
|
||||||
agi::Time max_time = max_value(&AssDialogue::Start, c->ass->Events);
|
if (!by_frame)
|
||||||
std::string value = by_frame ? std::to_string(c->videoController->FrameAtTime(max_time, agi::vfr::START)) : max_time.GetAssFormatted();
|
return helper(wxS("0:00:00.00"));
|
||||||
|
int frame = c->videoController->FrameAtTime(max_value(&AssDialogue::Start, c->ass->Events), agi::vfr::START);
|
||||||
for (char &c : value) {
|
return helper(std::to_wstring(frame));
|
||||||
if (c >= '0' && c <= '9')
|
|
||||||
c = '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
return helper(value);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -227,15 +224,10 @@ struct GridColumnEndTime final : GridColumnTime {
|
||||||
}
|
}
|
||||||
|
|
||||||
int Width(const agi::Context *c, WidthHelper &helper) const override {
|
int Width(const agi::Context *c, WidthHelper &helper) const override {
|
||||||
agi::Time max_time = max_value(&AssDialogue::End, c->ass->Events);
|
if (!by_frame)
|
||||||
std::string value = by_frame ? std::to_string(c->videoController->FrameAtTime(max_time, agi::vfr::END)) : max_time.GetAssFormatted();
|
return helper(wxS("0:00:00.00"));
|
||||||
|
int frame = c->videoController->FrameAtTime(max_value(&AssDialogue::End, c->ass->Events), agi::vfr::END);
|
||||||
for (char &c : value) {
|
return helper(std::to_wstring(frame));
|
||||||
if (c >= '0' && c <= '9')
|
|
||||||
c = '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
return helper(value);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -28,19 +28,6 @@ class wxMenu;
|
||||||
class wxMenuBar;
|
class wxMenuBar;
|
||||||
class wxWindow;
|
class wxWindow;
|
||||||
|
|
||||||
/*
|
|
||||||
ID allocation for menu items:
|
|
||||||
|
|
||||||
... - wxID_ANY(-1), wxID_LOWEST(4999) - wxID_HIGHEST(5999) Reserved by wxWidgets, see documentation for wxID_HIGHEST
|
|
||||||
|
|
||||||
(wxID_HIGHEST + 1) + 2000 ~ (wxID_HIGHEST + 1) + 2014 Grid column list, see base_grid.cpp
|
|
||||||
(wxID_HIGHEST + 1) + 3000 ~ (wxID_HIGHEST + 1) + 3001 Context menu, see timeedit_ctrl.cpp
|
|
||||||
(wxID_HIGHEST + 1) + 4000 ~ (wxID_HIGHEST + 1) + 7999 Context menu, see subs_edit_ctrl.cpp
|
|
||||||
(wxID_HIGHEST + 1) + 8000 ~ (wxID_HIGHEST + 1) + 8019 Grid context menu, see base_grid.cpp
|
|
||||||
(wxID_HIGHEST + 1) + 9000 ~ (wxID_HIGHEST + 1) + 9004 Video context menu, see video_display.cpp
|
|
||||||
(wxID_HIGHEST + 1) + 10000 ~ (wxID_HIGHEST + 1) + 10999 Main menu
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace menu {
|
namespace menu {
|
||||||
DEFINE_EXCEPTION(Error, agi::Exception);
|
DEFINE_EXCEPTION(Error, agi::Exception);
|
||||||
DEFINE_EXCEPTION(UnknownMenu, Error);
|
DEFINE_EXCEPTION(UnknownMenu, Error);
|
||||||
|
@ -52,7 +39,7 @@ namespace menu {
|
||||||
/// Throws:
|
/// Throws:
|
||||||
/// UnknownMenu if no menu with the given name was found
|
/// UnknownMenu if no menu with the given name was found
|
||||||
/// BadMenu if there is a menu with the given name, but it is invalid
|
/// BadMenu if there is a menu with the given name, but it is invalid
|
||||||
void GetMenuBar(std::string const& name, wxFrame *window, int id_base, agi::Context *c);
|
void GetMenuBar(std::string const& name, wxFrame *window, agi::Context *c);
|
||||||
|
|
||||||
/// @brief Get the menu with the specified name as a wxMenu
|
/// @brief Get the menu with the specified name as a wxMenu
|
||||||
/// @param name Name of the menu
|
/// @param name Name of the menu
|
||||||
|
@ -60,7 +47,7 @@ namespace menu {
|
||||||
/// Throws:
|
/// Throws:
|
||||||
/// UnknownMenu if no menu with the given name was found
|
/// UnknownMenu if no menu with the given name was found
|
||||||
/// BadMenu if there is a menu with the given name, but it is invalid
|
/// BadMenu if there is a menu with the given name, but it is invalid
|
||||||
std::unique_ptr<wxMenu> GetMenu(std::string const& name, int id_base, agi::Context *c);
|
std::unique_ptr<wxMenu> GetMenu(std::string const& name, agi::Context *c);
|
||||||
|
|
||||||
/// @brief Open a popup menu at the mouse
|
/// @brief Open a popup menu at the mouse
|
||||||
/// @param menu Menu to open
|
/// @param menu Menu to open
|
||||||
|
|
|
@ -16,8 +16,7 @@
|
||||||
"Save Charset" : "UTF-8",
|
"Save Charset" : "UTF-8",
|
||||||
"Save UI State" : true,
|
"Save UI State" : true,
|
||||||
"Show Toolbar" : true,
|
"Show Toolbar" : true,
|
||||||
"Toolbar Icon Size" : 16,
|
"Toolbar Icon Size" : 16
|
||||||
"Dark Mode" : false
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,7 +66,7 @@
|
||||||
"Next Line on Commit" : true,
|
"Next Line on Commit" : true,
|
||||||
"Player" : "",
|
"Player" : "",
|
||||||
"Plays When Stepping Video" : false,
|
"Plays When Stepping Video" : false,
|
||||||
"Provider" : "FFmpegSource",
|
"Provider" : "ffmpegsource",
|
||||||
"Renderer" : {
|
"Renderer" : {
|
||||||
"Spectrum" : {
|
"Spectrum" : {
|
||||||
"Cutoff" : 0,
|
"Cutoff" : 0,
|
||||||
|
@ -352,7 +351,7 @@
|
||||||
"Aegisub Cache" : true
|
"Aegisub Cache" : true
|
||||||
},
|
},
|
||||||
"VapourSynth" : {
|
"VapourSynth" : {
|
||||||
"Default Script" : "# This default script will load an audio file using BestSource.\n# It requires the `bs` plugin.\n\nimport vapoursynth as vs\nimport aegisub_vs as a\na.set_paths(locals())\n\na.ensure_plugin(\"bs\", \"BestSource\", \"To use Aegisub's default audio loader, the `bs` plugin for VapourSynth must be installed\")\nvs.core.bs.AudioSource(source=filename).set_output()"
|
"Default Script" : "# This default script will load an audio file using BestAudioSource.\n# It requires the `bas` plugin.\n\nimport vapoursynth as vs\nimport aegisub_vs as a\na.set_paths(locals())\n\na.ensure_plugin(\"bas\", \"BestAudioSource\", \"To use Aegisub's default audio loader, the `bas` plugin for VapourSynth must be installed\")\nvs.core.bas.Source(source=filename).set_output()"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Avisynth" : {
|
"Avisynth" : {
|
||||||
|
@ -373,7 +372,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"VapourSynth" : {
|
"VapourSynth" : {
|
||||||
"Autoload User Plugins": true,
|
|
||||||
"Cache" : {
|
"Cache" : {
|
||||||
"Files" : 500,
|
"Files" : 500,
|
||||||
"Size" : 1000
|
"Size" : 1000
|
||||||
|
@ -390,12 +388,11 @@
|
||||||
"BestSource" : {
|
"BestSource" : {
|
||||||
"Max Cache Size" : 1024,
|
"Max Cache Size" : 1024,
|
||||||
"Threads" : 0,
|
"Threads" : 0,
|
||||||
"Apply RFF": true,
|
|
||||||
"Seek Preroll" : 12
|
"Seek Preroll" : 12
|
||||||
},
|
},
|
||||||
"VapourSynth" : {
|
"VapourSynth" : {
|
||||||
"Log Level": "Information",
|
"Log Level": "Information",
|
||||||
"Default Script" : "# This default script will load a video file using LWLibavSource.\n# It requires the `lsmas` plugin.\n# See ?data/automation/vapoursynth/aegisub_vs.py for more information.\n\nimport vapoursynth as vs\nimport time\nimport aegisub_vs as a\na.set_paths(locals())\n\nclip, videoinfo = a.wrap_lwlibavsource(filename)\nclip.set_output()\n__aegi_timecodes = videoinfo[\"timecodes\"]\n__aegi_keyframes = videoinfo[\"keyframes\"]\n\n# Uncomment this line to make Aegisub look for a keyframes file for the video, or ask to detect keyframes on scene changes if no file was found.\n# You can also change the GenKeyframesMode. Valid values are NEVER, ALWAYS, and ASK.\n#__aegi_keyframes = a.get_keyframes(filename, clip, __aegi_keyframes, generate=a.GenKeyframesMode.ASK)\n\n# Check if the file has an audio track. This requires the `bs` plugin.\n__aegi_hasaudio = 1 if a.check_audio(filename) else 0"
|
"Default Script" : "# This default script will load a video file using LWLibavSource.\n# It requires the `lsmas` plugin.\n# See ?data/automation/vapoursynth/aegisub_vs.py for more information.\n\nimport vapoursynth as vs\nimport time\nimport aegisub_vs as a\na.set_paths(locals())\n\nclip, videoinfo = a.wrap_lwlibavsource(filename)\nclip.set_output()\n__aegi_timecodes = videoinfo[\"timecodes\"]\n__aegi_keyframes = videoinfo[\"keyframes\"]\n\n# Uncomment the first following line to read keyframes from a file when present.\n#__aegi_keyframes = a.try_get_keyframes(filename, __aegi_keyframes)\n\n# Uncomment the following line to automatically generate keyframes at scene changes. This will take some time when first loading a video.\n__aegi_keyframes = a.get_keyframes(filename, clip)\n\n# Check if the file has an audio track. This requires the `bas` plugin.\n__aegi_hasaudio = 1 if a.check_audio(filename) else 0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -625,7 +622,6 @@
|
||||||
"Grid": false,
|
"Grid": false,
|
||||||
"Org Mode": 0
|
"Org Mode": 0
|
||||||
},
|
},
|
||||||
"Shape Handle Size": 3,
|
|
||||||
"Autohide": false
|
"Autohide": false
|
||||||
},
|
},
|
||||||
"Align to Video" : {
|
"Align to Video" : {
|
||||||
|
@ -652,7 +648,7 @@
|
||||||
"Maximized" : false
|
"Maximized" : false
|
||||||
},
|
},
|
||||||
"Dummy" : {
|
"Dummy" : {
|
||||||
"FPS String" : "24000/1001",
|
"FPS" : 23.975999999999999091,
|
||||||
"Last" : {
|
"Last" : {
|
||||||
"Height" : 720,
|
"Height" : 720,
|
||||||
"Length" : 40000,
|
"Length" : 40000,
|
||||||
|
@ -663,7 +659,7 @@
|
||||||
"Last Script Resolution Mismatch Choice" : 2,
|
"Last Script Resolution Mismatch Choice" : 2,
|
||||||
"Open Audio" : true,
|
"Open Audio" : true,
|
||||||
"Overscan Mask" : false,
|
"Overscan Mask" : false,
|
||||||
"Provider" : "FFmpegSource",
|
"Provider" : "ffmpegsource",
|
||||||
"Script Resolution Mismatch" : 1,
|
"Script Resolution Mismatch" : 1,
|
||||||
"Slider" : {
|
"Slider" : {
|
||||||
"Fast Jump Step" : 10,
|
"Fast Jump Step" : 10,
|
||||||
|
|
|
@ -2,9 +2,7 @@
|
||||||
"Audio" : {
|
"Audio" : {
|
||||||
"Player" : "DirectSound"
|
"Player" : "DirectSound"
|
||||||
},
|
},
|
||||||
"Provider" : {
|
"Subtitle" : {
|
||||||
"VapourSynth" : {
|
"Provider" : "CSRI/xy-vsfilter_aegisub"
|
||||||
"Autoload User Plugins": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,10 +125,10 @@
|
||||||
"edit/line/delete" : [
|
"edit/line/delete" : [
|
||||||
"Ctrl-Delete"
|
"Ctrl-Delete"
|
||||||
],
|
],
|
||||||
"edit/line/split/before" : [
|
"edit/line/duplicate/shift" : [
|
||||||
"Ctrl-D"
|
"Ctrl-D"
|
||||||
],
|
],
|
||||||
"edit/line/split/after" : [
|
"edit/line/duplicate/shift_back" : [
|
||||||
"Ctrl-Shift-D"
|
"Ctrl-Shift-D"
|
||||||
],
|
],
|
||||||
"edit/line/paste" : [
|
"edit/line/paste" : [
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue