Compare commits

..

8 commits
mia ... mia

Author SHA1 Message Date
Mia Herkt bf532779a2
Icons based on Breeze 2023-03-26 08:56:39 +02:00
Mia Herkt a3024815a0
Properly skip events
So the mouse doesn’t get trapped because the button release event is
swallowed up.
2023-03-26 08:56:36 +02:00
Mia Herkt 774ff2bbaf
audio display: limit mouse event rate
Same as with visual typesetting.
2023-03-26 08:56:35 +02:00
Mia Herkt d2b3e38565
visual typesetting: limit event frequency
This change makes Aegisub a lot more responsive on platforms like X11
where mouse events may come in at very high frequencies and/or in bursts.
2023-03-26 08:56:34 +02:00
Mia Herkt 9b3fa75ab2
Visual tools: Make X/Y rotation less sensitive 2023-03-26 08:56:33 +02:00
Mia Herkt 45fbd74f12
Hack shortcuts to set colors from video display 2023-03-26 08:56:32 +02:00
Mia Herkt 54d8bde136
Remove annoying modal dialogs 2023-03-26 08:56:31 +02:00
Mia Herkt cb1157a308
Fix video toolbox keyframe color 2023-03-26 08:56:27 +02:00
183 changed files with 173102 additions and 215876 deletions

View file

@ -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
View file

@ -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

View file

@ -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`

View file

@ -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

View file

@ -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

View file

@ -1,5 +1,4 @@
subdir('include') subdir('include')
subdir('vapoursynth')
automation_dir = dataroot / 'automation' automation_dir = dataroot / 'automation'

View file

@ -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.
---

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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;
} }

View file

@ -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)

View file

@ -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;

View file

@ -24,8 +24,6 @@
#include <memory> #include <memory>
#include <string> #include <string>
#pragma once
#undef CreateDirectory #undef CreateDirectory
namespace agi { namespace agi {

View file

@ -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 {

View file

@ -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)

View file

@ -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

View file

@ -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();

View file

@ -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,

View file

@ -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')

View file

@ -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>

View 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>

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

File diff suppressed because it is too large Load diff

11587
po/ar.po

File diff suppressed because it is too large Load diff

10648
po/bg.po

File diff suppressed because it is too large Load diff

16087
po/ca.po

File diff suppressed because it is too large Load diff

13253
po/cs.po

File diff suppressed because it is too large Load diff

12061
po/da.po

File diff suppressed because it is too large Load diff

11102
po/de.po

File diff suppressed because it is too large Load diff

12637
po/el.po

File diff suppressed because it is too large Load diff

12952
po/es.po

File diff suppressed because it is too large Load diff

14406
po/eu.po

File diff suppressed because it is too large Load diff

12033
po/fa.po

File diff suppressed because it is too large Load diff

12530
po/fi.po

File diff suppressed because it is too large Load diff

11506
po/fr_FR.po

File diff suppressed because it is too large Load diff

11715
po/gl.po

File diff suppressed because it is too large Load diff

14714
po/hu.po

File diff suppressed because it is too large Load diff

14408
po/id.po

File diff suppressed because it is too large Load diff

13518
po/it.po

File diff suppressed because it is too large Load diff

12135
po/ja.po

File diff suppressed because it is too large Load diff

13052
po/ko.po

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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)

11750
po/nl.po

File diff suppressed because it is too large Load diff

12160
po/pl.po

File diff suppressed because it is too large Load diff

12526
po/pt_BR.po

File diff suppressed because it is too large Load diff

13261
po/pt_PT.po

File diff suppressed because it is too large Load diff

11634
po/ru.po

File diff suppressed because it is too large Load diff

13263
po/sr_RS.po

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

11505
po/uk_UA.po

File diff suppressed because it is too large Load diff

15787
po/vi.po

File diff suppressed because it is too large Load diff

12412
po/zh_CN.po

File diff suppressed because it is too large Load diff

12393
po/zh_TW.po

File diff suppressed because it is too large Load diff

View file

@ -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));
} }
} }

View file

@ -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;

View file

@ -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)});
} }
} }

View file

@ -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"));

View file

@ -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))

View file

@ -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);
} }
} }

View file

@ -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();

View file

@ -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>

View file

@ -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);

View file

@ -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;
} }

View file

@ -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

View file

@ -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;
} }

View file

@ -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; }

View file

@ -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() {

View file

@ -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 {

View file

@ -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

View file

@ -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 */

View file

@ -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);

View file

@ -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() + "\"");
} }
}; };

View file

@ -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 {

View file

@ -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;
} }
}; };

View file

@ -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

View file

@ -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);
} }

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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;
}
}
}
}
} }

View file

@ -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;
} }

View file

@ -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);

View file

@ -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);

View file

@ -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
}; };

View file

@ -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;
} }

View file

@ -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);

View file

@ -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);
} }
}; };

View file

@ -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

View file

@ -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,

View file

@ -2,9 +2,7 @@
"Audio" : { "Audio" : {
"Player" : "DirectSound" "Player" : "DirectSound"
}, },
"Provider" : { "Subtitle" : {
"VapourSynth" : { "Provider" : "CSRI/xy-vsfilter_aegisub"
"Autoload User Plugins": false
}
} }
} }

View file

@ -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