Compare commits
160 Commits
Author | SHA1 | Date |
---|---|---|
Mia Herkt | a87d03c9e8 | |
Mia Herkt | fea5ca3520 | |
Mia Herkt | 71fb001d25 | |
Mia Herkt | ca1ac8bcea | |
Mia Herkt | d40eb355dd | |
Mia Herkt | 6c67b9970b | |
Mia Herkt | a5232bc49e | |
Mia Herkt | 791fd4cf43 | |
arch1t3cht | 6af7c6bddf | |
0tkl | 167b4add50 | |
0tkl | 29c8054b50 | |
arch1t3cht | b0c77beef6 | |
arch1t3cht | 8650e12364 | |
arch1t3cht | eca52e4166 | |
arch1t3cht | 8e60a46c01 | |
arch1t3cht | 1eea4ca69c | |
arch1t3cht | 7f928e8d6d | |
arch1t3cht | 592bd993d7 | |
arch1t3cht | 160cf46b98 | |
arch1t3cht | df9d442102 | |
arch1t3cht | ce557d98e4 | |
arch1t3cht | 786f082673 | |
arch1t3cht | ffb95f1375 | |
arch1t3cht | 8947e20dbb | |
arch1t3cht | 18d088c479 | |
arch1t3cht | 20caaabc07 | |
arch1t3cht | 928a2d4c6c | |
arch1t3cht | 07fa563fa6 | |
arch1t3cht | 5ceaa45f3a | |
arch1t3cht | 4576ac0638 | |
arch1t3cht | 693959801f | |
arch1t3cht | 752a14cc25 | |
arch1t3cht | 24c8144e91 | |
arch1t3cht | 1ba7979ff4 | |
arch1t3cht | d728ce0e78 | |
arch1t3cht | fd753c58a5 | |
Setsugennoao | 568597bddc | |
arch1t3cht | 95500b84fd | |
arch1t3cht | 0d281af269 | |
arch1t3cht | fac664ad10 | |
arch1t3cht | 6dda04da9f | |
arch1t3cht | dd303b1c9d | |
arch1t3cht | 2ac3bdf870 | |
arch1t3cht | 795dffc072 | |
arch1t3cht | 45fdaecd72 | |
arch1t3cht | 4a939d1954 | |
arch1t3cht | 47b10e5ffc | |
arch1t3cht | 2dbee37ad8 | |
arch1t3cht | a1b3e0d9f1 | |
arch1t3cht | 79b3f4ccb0 | |
arch1t3cht | 4ebdc2b82c | |
arch1t3cht | fabc6e436f | |
arch1t3cht | 858f4acf35 | |
arch1t3cht | a9eed184c2 | |
arch1t3cht | dd3016a89d | |
arch1t3cht | 07c4f26a8e | |
arch1t3cht | b3eb182259 | |
arch1t3cht | ee247419b6 | |
arch1t3cht | b8f4c98c4c | |
arch1t3cht | c67ba9f70c | |
arch1t3cht | 9bc3cad79e | |
arch1t3cht | 82b7e96cea | |
arch1t3cht | a631bf192c | |
arch1t3cht | ad92ccf01a | |
arch1t3cht | 4e6af75db4 | |
arch1t3cht | 61ec2bc3e7 | |
arch1t3cht | edae653584 | |
arch1t3cht | 8fb8e90006 | |
arch1t3cht | 49139f0a22 | |
arch1t3cht | d10ffebe35 | |
arch1t3cht | 9acb673457 | |
arch1t3cht | 1fde843630 | |
arch1t3cht | 4a97bb0dd2 | |
Alex James | 4f67db8dd7 | |
arch1t3cht | 41ef3fa56a | |
arch1t3cht | 11fece4c03 | |
arch1t3cht | b2ee8ac036 | |
arch1t3cht | a867f0cc30 | |
arch1t3cht | 418514456e | |
sepro | 061a860e2e | |
sepro | 5944d7999c | |
sepro | 0f897ba0eb | |
arch1t3cht | 4659bb2802 | |
sepro | 0637bcdc7f | |
arch1t3cht | 0b7ea58cd6 | |
arch1t3cht | 239d585512 | |
arch1t3cht | c61f149a37 | |
arch1t3cht | 47c923d4ed | |
arch1t3cht | 263c2b9189 | |
arch1t3cht | 66127f8c40 | |
0tkl | 0fd12795da | |
arch1t3cht | a957af9a12 | |
arch1t3cht | 6754ff8775 | |
arch1t3cht | f9be4a854f | |
arch1t3cht | d97c16cb7c | |
arch1t3cht | d3325eef6c | |
arch1t3cht | dbe30b4da5 | |
arch1t3cht | 02567c2265 | |
arch1t3cht | 7ca8b4c008 | |
arch1t3cht | 644a4ca9f7 | |
arch1t3cht | 96123cb6da | |
arch1t3cht | 42c71c4775 | |
arch1t3cht | 0479b310e8 | |
arch1t3cht | 3f017bc29c | |
arch1t3cht | eb6f31c077 | |
arch1t3cht | e483c5e48f | |
arch1t3cht | 9e8ac83998 | |
arch1t3cht | 1f6684823c | |
arch1t3cht | fd401f059a | |
arch1t3cht | d06a31968d | |
arch1t3cht | 7bc18fec26 | |
arch1t3cht | b41f6bde71 | |
arch1t3cht | 58d6ab520b | |
arch1t3cht | 58c0130d81 | |
arch1t3cht | dba4f4924e | |
arch1t3cht | f3d6eea3b9 | |
wangqr | 055c87cd21 | |
arch1t3cht | 42f7e53e92 | |
arch1t3cht | 79050dfdfb | |
arch1t3cht | 64b92a95ac | |
arch1t3cht | 5e51c59e8e | |
arch1t3cht | c8f8e8ac42 | |
arch1t3cht | 4365b16524 | |
arch1t3cht | b150b6bfc7 | |
Oneric | a6cf70ba59 | |
Ananji Peixoto da Costa | 0e9dd4b311 | |
Oneric | 2f4258a8b4 | |
Oneric | 5b66d473d2 | |
Oneric | da9842b70c | |
Oneric | 6b3cb7ec63 | |
Oneric | bc3358fcfe | |
Oneric | f417f6f1ad | |
Oneric | 6d9901ee3c | |
Oneric | afa290ecfb | |
Oneric | 87d810254c | |
Oneric | 5c00368e85 | |
Oneric | 8c35b1d642 | |
Oneric | 2b33e45f23 | |
Oneric | 66efa84eed | |
Oneric | 429455263f | |
arch1t3cht | db0e79323f | |
arch1t3cht | 3d278547fe | |
arch1t3cht | ff20805ae6 | |
arch1t3cht | 20cc0b8077 | |
arch1t3cht | 9c2d6169c6 | |
arch1t3cht | 82dffcb9f9 | |
arch1t3cht | 00e241d74b | |
arch1t3cht | 0c057ebddb | |
arch1t3cht | 0f13a75a42 | |
arch1t3cht | 18fd966bd9 | |
arch1t3cht | 165cb14879 | |
arch1t3cht | 43d65b906b | |
arch1t3cht | 26a5f00c8a | |
arch1t3cht | 8633e2c4ae | |
LightArrowsEXE | 178551071e | |
arch1t3cht | 9a7314015f | |
arch1t3cht | af9d659c93 | |
arch1t3cht | e7e56ef5e0 | |
arch1t3cht | 5050bad8ac | |
arch1t3cht | d2c46f5dca |
|
@ -10,6 +10,7 @@ on:
|
|||
branches:
|
||||
- master
|
||||
- feature
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -34,12 +35,35 @@ jobs:
|
|||
-Dharfbuzz:gobject=disabled
|
||||
-Dharfbuzz:tests=disabled
|
||||
-Dharfbuzz:docs=disabled
|
||||
-Dharfbuzz:icu=disabled
|
||||
-Dfribidi:tests=false
|
||||
-Dfribidi:docs=false
|
||||
-Dlibass:fontconfig=disabled
|
||||
-Davisynth=enabled
|
||||
-Dbestsource=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
|
||||
-Davisynth=enabled
|
||||
-Dbestsource=enabled
|
||||
-Dvapoursynth=enabled
|
||||
-Dwx_version='3.3.0'
|
||||
#- {
|
||||
# name: Windows MinGW,
|
||||
# os: windows-latest,
|
||||
|
@ -57,11 +81,24 @@ jobs:
|
|||
buildtype: release,
|
||||
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
|
||||
-Davisynth=enabled
|
||||
-Dbestsource=enabled
|
||||
-Dvapoursynth=enabled
|
||||
- {
|
||||
name: macOS Release,
|
||||
os: macos-latest,
|
||||
buildtype: release,
|
||||
args: -Ddefault_library=static -Dbuild_osx_bundle=true -Dlocal_boost=true
|
||||
args: -Ddefault_library=static -Dbuild_osx_bundle=true -Dlocal_boost=true -Dvapoursynth=enabled --force-fallback-for=ffms2
|
||||
}
|
||||
|
||||
steps:
|
||||
|
@ -75,7 +112,7 @@ jobs:
|
|||
|
||||
- name: Setup Meson
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --upgrade pip setuptools
|
||||
pip install meson
|
||||
|
||||
- name: Setup MSVC
|
||||
|
@ -104,13 +141,13 @@ jobs:
|
|||
brew install pulseaudio # NO OpenAL in github CI
|
||||
|
||||
- name: Install dependencies (Linux)
|
||||
if: matrix.config.os == 'ubuntu-latest'
|
||||
if: startsWith(matrix.config.os, 'ubuntu-')
|
||||
run: |
|
||||
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
|
||||
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 libjansson-dev nasm
|
||||
|
||||
- name: Configure
|
||||
run: meson build ${{ matrix.config.args }} -Dbuildtype=${{ matrix.config.buildtype }}
|
||||
run: meson setup build ${{ matrix.config.args }} -Dbuildtype=${{ matrix.config.buildtype }}
|
||||
|
||||
- name: Build
|
||||
run: meson compile -C build
|
||||
|
@ -159,3 +196,27 @@ jobs:
|
|||
name: ${{ matrix.config.name }} - installer
|
||||
path: build/Aegisub-*.dmg
|
||||
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@v3
|
||||
if: matrix.config.appimage
|
||||
with:
|
||||
name: ${{ matrix.config.name }}
|
||||
path: appimage/*.AppImage
|
||||
if-no-files-found: error
|
||||
|
|
13
README.md
|
@ -1,5 +1,7 @@
|
|||
## arch1t3cht's Aegisub "fork"
|
||||
Go [here](#branchfeature-list) for the new features.
|
||||
Download release builds [here](https://github.com/arch1t3cht/Aegisub/releases), or the latest CI builds [here](https://github.com/arch1t3cht/Aegisub/actions).
|
||||
|
||||
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??
|
||||
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,
|
||||
|
@ -50,9 +52,6 @@ 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`.
|
||||
|
||||
#### 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
|
||||
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.
|
||||
|
||||
|
@ -63,11 +62,13 @@ If you're compiling yourself, try adding `--force-fallback-for=zlib` to the meso
|
|||
|
||||
|
||||
### 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.
|
||||
|
||||
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 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 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.
|
||||
If all dependencies are installed:
|
||||
- Install Meson
|
||||
- Clone the repository
|
||||
|
@ -132,7 +133,7 @@ All other dependencies are either stored in the repository or are included as su
|
|||
|
||||
Building:
|
||||
|
||||
1. Clone Aegisub's repository: `git clone https://github.com/TypesettingTools/Aegisub.git`
|
||||
1. Clone Aegisub's repository: `git clone https://github.com/arch1t3cht/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`)
|
||||
3. Build with `cd build` and `ninja`
|
||||
|
||||
|
|
|
@ -23,10 +23,10 @@ OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
|
||||
cleantags_version = "1.301"
|
||||
cleantags_modified = "13 November 2009"
|
||||
cleantags_version = "1.302"
|
||||
cleantags_modified = "12 October 2023"
|
||||
|
||||
ktag = "\\[kK][fo]?%d+"
|
||||
ktag = "\\[kK][fo]?[%d.]+"
|
||||
|
||||
--[[ The main function that performs the cleaning up
|
||||
Takes: text
|
||||
|
|
|
@ -100,7 +100,7 @@ function io.open(fname, mode)
|
|||
local file = assert(orig_open("nul", "rb"))
|
||||
if ffi.C._wfreopen(wfname, wmode, file) == nil then
|
||||
local msg, errno = select(2, file:close())
|
||||
return nil, fname .. ": " .. msg, errno
|
||||
return nil, fname .. ": " .. tostring(msg), errno
|
||||
end
|
||||
|
||||
return file
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
subdir('include')
|
||||
subdir('vapoursynth')
|
||||
|
||||
automation_dir = dataroot / 'automation'
|
||||
|
||||
|
|
|
@ -22,14 +22,18 @@ Aegisub using the following variables:
|
|||
- __aegi_hasaudio: int: If nonzero, Aegisub will try to load an audio track
|
||||
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
|
||||
other data.
|
||||
"""
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
from enum import Enum
|
||||
from collections import deque
|
||||
from typing import Any, Dict, List, Tuple
|
||||
from typing import Any, Dict, List, Tuple, Callable
|
||||
|
||||
import vapoursynth as vs
|
||||
core = vs.core
|
||||
|
@ -39,6 +43,28 @@ aegi_vsplugins: str = ""
|
|||
|
||||
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):
|
||||
"""
|
||||
Initialize the wrapper library with the given configuration directories.
|
||||
|
@ -97,16 +123,19 @@ 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_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]+)")
|
||||
videoindex_re = re.compile(r"<ActiveVideoStreamIndex>(?P<VideoStreamIndex>[0-9+]+)</ActiveVideoStreamIndex>")
|
||||
|
||||
class LWIndexFrame:
|
||||
pts: int
|
||||
key: int
|
||||
index: int
|
||||
|
||||
def __init__(self, raw: list[str]):
|
||||
match1 = lwindex_re1.match(raw[0])
|
||||
match2 = lwindex_re2.match(raw[1])
|
||||
if not match1 or not match2:
|
||||
raise ValueError("Invalid lwindex format")
|
||||
self.index = int(match1.group("Index"))
|
||||
self.pts = int(match1.group("PTS"))
|
||||
self.key = int(match2.group("Key"))
|
||||
|
||||
|
@ -124,11 +153,19 @@ def info_from_lwindex(indexfile: str) -> Dict[str, List[int]]:
|
|||
with open(indexfile, encoding="latin1") as f:
|
||||
index = f.read().splitlines()
|
||||
|
||||
indexstart, indexend = index.index("</StreamInfo>") + 1, index.index("</LibavReaderIndex>")
|
||||
videoindex_str = next(l for l in index if l.startswith("<ActiveVideoStreamIndex>"))
|
||||
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 = [f for f in frames if f.index == videoindex] # select the first stream
|
||||
frames.sort(key=int)
|
||||
|
||||
streaminfo = streaminfo_re.match(index[indexstart - 2])
|
||||
streaminfo = streaminfo_re.match(index[index.index(f"<StreamInfo={videoindex},0>") + 1]) # info of first stream
|
||||
if not streaminfo:
|
||||
raise ValueError("Invalid lwindex format")
|
||||
|
||||
|
@ -156,6 +193,9 @@ def wrap_lwlibavsource(filename: str, cachedir: str | None = None, **kwargs: Any
|
|||
pass
|
||||
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")
|
||||
|
||||
if b"-Dcachedir" not in core.lsmas.Version()["config"]: # type: ignore
|
||||
|
@ -163,6 +203,7 @@ def wrap_lwlibavsource(filename: str, cachedir: str | None = None, **kwargs: Any
|
|||
|
||||
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)
|
||||
|
||||
|
||||
|
@ -171,7 +212,6 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
|
|||
**kwargs: Any) -> List[int]:
|
||||
"""
|
||||
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 use_scxvid: Whether to use Scxvid. If False, the function uses WWXD.
|
||||
|
@ -181,6 +221,9 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
|
|||
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)
|
||||
|
||||
if use_scxvid:
|
||||
|
@ -196,12 +239,12 @@ def make_keyframes(clip: vs.VideoNode, use_scxvid: bool = False,
|
|||
nonlocal done
|
||||
keyframes[n] = f.props._SceneChangePrev if use_scxvid else f.props.Scenechange # type: ignore
|
||||
done += 1
|
||||
if done % (clip.num_frames // 25) == 0:
|
||||
vs.core.log_message(vs.MESSAGE_TYPE_INFORMATION, "Detecting keyframes... {}% done.\n".format(100 * done // clip.num_frames))
|
||||
if done % (clip.num_frames // 200) == 0:
|
||||
progress_set_progress(100 * done / clip.num_frames)
|
||||
return f
|
||||
|
||||
deque(clip.std.ModifyFrame(clip, _cb).frames(close=True), 0)
|
||||
vs.core.log_message(vs.MESSAGE_TYPE_INFORMATION, "Done detecting keyframes.\n")
|
||||
progress_set_progress(100)
|
||||
return [n for n in range(clip.num_frames) if keyframes[n]]
|
||||
|
||||
|
||||
|
@ -216,26 +259,49 @@ def save_keyframes(filename: str, keyframes: List[int]):
|
|||
f.write("".join(f"{n}\n" for n in keyframes))
|
||||
|
||||
|
||||
def try_get_keyframes(filename: str, default: str | List[int]) -> str | List[int]:
|
||||
"""
|
||||
Checks if a keyframes file for the given filename is present and, if so,
|
||||
returns it. Otherwise, returns the given list of keyframes.
|
||||
"""
|
||||
kffilename = make_keyframes_filename(filename)
|
||||
|
||||
return kffilename if os.path.exists(kffilename) else default
|
||||
class GenKeyframesMode(Enum):
|
||||
NEVER = 0
|
||||
ALWAYS = 1
|
||||
ASK = 2
|
||||
|
||||
|
||||
def get_keyframes(filename: str, clip: vs.VideoNode, **kwargs: Any) -> str:
|
||||
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]:
|
||||
"""
|
||||
When not already present, creates a keyframe file for the given clip next
|
||||
Looks for a keyframes file for the given filename.
|
||||
If no file was found, this function can generate a keyframe file for the given clip next
|
||||
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.
|
||||
"""
|
||||
progress_set_message("Looking for keyframes")
|
||||
progress_set_indeterminate()
|
||||
|
||||
kffilename = make_keyframes_filename(filename)
|
||||
|
||||
if not os.path.exists(kffilename):
|
||||
vs.core.log_message(vs.MESSAGE_TYPE_INFORMATION, "No keyframes file found, detecting keyframes...\n")
|
||||
if generate == GenKeyframesMode.NEVER:
|
||||
return fallback
|
||||
if generate == GenKeyframesMode.ASK and not ask_callback(filename):
|
||||
return fallback
|
||||
|
||||
keyframes = make_keyframes(clip, **kwargs)
|
||||
save_keyframes(kffilename, keyframes)
|
||||
|
||||
|
@ -249,6 +315,8 @@ def check_audio(filename: str, **kwargs: Any) -> bool:
|
|||
won't crash if it's not installed.
|
||||
Additional keyword arguments are passed on to BestAudioSource.
|
||||
"""
|
||||
progress_set_message("Checking if the file has an audio track")
|
||||
progress_set_indeterminate()
|
||||
try:
|
||||
ensure_plugin("bas", "BestAudioSource", "")
|
||||
vs.core.bas.Source(source=filename, **kwargs)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# Copy files to build directory for testing purposes
|
||||
vs_files = files(
|
||||
'aegisub_vs.py',
|
||||
)
|
||||
|
||||
foreach f: vs_files
|
||||
configure_file(input: f, output: '@PLAINNAME@', copy: true)
|
||||
endforeach
|
|
@ -23,7 +23,6 @@
|
|||
#include <boost/range/algorithm.hpp>
|
||||
|
||||
#include <libaegisub/charset_conv.h>
|
||||
#include <iconv.h>
|
||||
|
||||
#include "charset_6937.h"
|
||||
|
||||
|
@ -420,7 +419,7 @@ size_t IconvWrapper::DstStrLen(const char* str) {
|
|||
bool IsConversionSupported(const char *src, const char *dst) {
|
||||
iconv_t cd = iconv_open(dst, src);
|
||||
bool supported = cd != iconv_invalid;
|
||||
iconv_close(cd);
|
||||
if (supported) iconv_close(cd);
|
||||
return supported;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include <libaegisub/exception.h>
|
||||
#include <iconv.h>
|
||||
|
||||
namespace agi {
|
||||
namespace charset {
|
||||
|
@ -34,8 +35,6 @@ DEFINE_EXCEPTION(BufferTooSmall, ConversionFailure);
|
|||
DEFINE_EXCEPTION(BadInput, ConversionFailure);
|
||||
DEFINE_EXCEPTION(BadOutput, ConversionFailure);
|
||||
|
||||
typedef void *iconv_t;
|
||||
|
||||
/// RAII handle for iconv
|
||||
class Iconv {
|
||||
iconv_t cd;
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#pragma once
|
||||
|
||||
#undef CreateDirectory
|
||||
|
||||
namespace agi {
|
||||
|
|
|
@ -129,7 +129,6 @@ struct LuaStackcheck {
|
|||
void dump();
|
||||
|
||||
LuaStackcheck(lua_State *L) : L(L), startstack(lua_gettop(L)) { }
|
||||
~LuaStackcheck() { check_stack(0); }
|
||||
};
|
||||
#else
|
||||
struct LuaStackcheck {
|
||||
|
|
|
@ -84,6 +84,12 @@ public:
|
|||
Color const& GetColor() 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 SetInt(const int64_t);
|
||||
void SetDouble(const double);
|
||||
|
@ -96,6 +102,12 @@ public:
|
|||
std::vector<Color> const& GetListColor() 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 SetListInt(std::vector<int64_t>);
|
||||
void SetListDouble(std::vector<double>);
|
||||
|
@ -117,6 +129,7 @@ public:
|
|||
: OptionValue(std::move(member_name)) \
|
||||
, value(member_value), value_default(member_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(); } \
|
||||
OptionType GetType() const { return OptionType::type_name; } \
|
||||
void Reset() { value = value_default; NotifyChanged(); } \
|
||||
|
@ -141,6 +154,7 @@ CONFIG_OPTIONVALUE(Bool, bool)
|
|||
: OptionValue(std::move(name)) \
|
||||
, array(value), array_default(value) { } \
|
||||
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(); } \
|
||||
OptionType GetType() const { return OptionType::List##type_name; } \
|
||||
void Reset() { array = array_default; NotifyChanged(); } \
|
||||
|
@ -156,6 +170,7 @@ CONFIG_OPTIONVALUE_LIST(Bool, bool)
|
|||
|
||||
#define CONFIG_OPTIONVALUE_ACCESSORS(ReturnType, Type) \
|
||||
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)); }
|
||||
|
||||
CONFIG_OPTIONVALUE_ACCESSORS(std::string, String)
|
||||
|
|
|
@ -75,14 +75,13 @@ void Path::FillPlatformSpecificPaths() {
|
|||
SetToken("?local", home/".aegisub");
|
||||
|
||||
#ifdef APPIMAGE_BUILD
|
||||
agi::fs::path data = exe_dir();
|
||||
if (data == "") data = home/".aegisub";
|
||||
SetToken("?data", data);
|
||||
SetToken("?dictionary", Decode("?data/dictionaries"));
|
||||
agi::fs::path exe = exe_dir();
|
||||
agi::fs::path data_from_bin = agi::fs::path(P_DATA).lexically_relative(P_BIN);
|
||||
SetToken("?data", (exe != "" ? exe/data_from_bin : home/".aegisub").make_preferred());
|
||||
#else
|
||||
SetToken("?data", P_DATA);
|
||||
SetToken("?dictionary", "/usr/share/hunspell");
|
||||
#endif
|
||||
SetToken("?dictionary", "/usr/share/hunspell");
|
||||
|
||||
#else
|
||||
agi::fs::path app_support = agi::util::GetApplicationSupportDirectory();
|
||||
|
|
33
meson.build
|
@ -1,6 +1,6 @@
|
|||
project('Aegisub', ['c', 'cpp'],
|
||||
license: 'BSD-3-Clause',
|
||||
meson_version: '>=0.57.0',
|
||||
meson_version: '>=1.0.0',
|
||||
default_options: ['cpp_std=c++14', 'buildtype=debugoptimized'],
|
||||
version: '3.2.2')
|
||||
|
||||
|
@ -8,6 +8,7 @@ cmake = import('cmake')
|
|||
|
||||
if host_machine.system() == 'windows'
|
||||
add_project_arguments('-DNOMINMAX', language: 'cpp')
|
||||
add_project_arguments('-DUNICODE', language: 'cpp')
|
||||
|
||||
if not get_option('csri').disabled()
|
||||
add_global_arguments('-DCSRI_NO_EXPORT', language: 'c')
|
||||
|
@ -57,11 +58,15 @@ if get_option('buildtype') == 'release'
|
|||
endif
|
||||
|
||||
conf = configuration_data()
|
||||
conf.set_quoted('P_BIN', bindir)
|
||||
conf.set_quoted('P_DATA', dataroot)
|
||||
conf.set_quoted('P_LOCALE', localedir)
|
||||
if get_option('credit') != ''
|
||||
conf.set_quoted('BUILD_CREDIT', get_option('credit'))
|
||||
endif
|
||||
if get_option('build_appimage')
|
||||
conf.set('APPIMAGE_BUILD', 1)
|
||||
endif
|
||||
conf.set('WITH_UPDATE_CHECKER', get_option('enable_update_checker'))
|
||||
|
||||
deps = []
|
||||
|
@ -98,7 +103,7 @@ deps += dependency('libass', version: '>=0.9.7',
|
|||
|
||||
boost_modules = ['chrono', 'filesystem', 'thread', 'locale', 'regex']
|
||||
if not get_option('local_boost')
|
||||
boost_dep = dependency('boost', version: '>=1.50.0',
|
||||
boost_dep = dependency('boost', version: '>=1.60.0',
|
||||
modules: boost_modules + ['system'],
|
||||
required: false,
|
||||
static: get_option('default_library') == 'static')
|
||||
|
@ -142,16 +147,21 @@ else
|
|||
opt_var = cmake.subproject_options()
|
||||
opt_var.add_cmake_defines({
|
||||
'wxBUILD_INSTALL': false,
|
||||
'wxBUILD_PRECOMP': false, # otherwise breaks project generation w/ meson
|
||||
'wxBUILD_PRECOMP': 'OFF', # otherwise breaks project generation w/ meson
|
||||
'wxBUILD_SHARED': build_shared,
|
||||
|
||||
'wxUSE_WEBVIEW': false, # breaks build on linux
|
||||
'wxUSE_REGEX': 'builtin',
|
||||
'CMAKE_BUILD_TYPE': build_type,
|
||||
'wxUSE_IMAGE': true,
|
||||
'wxBUILD_MONOLITHIC': true # otherwise breaks project generation w/ meson
|
||||
})
|
||||
|
||||
wx = cmake.subproject('wxWidgets', options: opt_var)
|
||||
if get_option('wx_version').version_compare('>=3.3.0')
|
||||
wx = cmake.subproject('wxWidgets-master', options: opt_var)
|
||||
else
|
||||
wx = cmake.subproject('wxWidgets', options: opt_var)
|
||||
endif
|
||||
|
||||
deps += [
|
||||
wx.dependency('wxmono'),
|
||||
|
@ -159,6 +169,12 @@ else
|
|||
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'
|
||||
deps += [
|
||||
wx.dependency('wxpng'),
|
||||
|
@ -312,7 +328,7 @@ if host_machine.system() == 'windows'
|
|||
endif
|
||||
|
||||
if host_machine.system() == 'darwin'
|
||||
frameworks_dep = dependency('appleframeworks', modules : ['CoreText', 'CoreFoundation', 'AppKit', 'Carbon', 'IOKit'])
|
||||
frameworks_dep = dependency('appleframeworks', modules : ['CoreText', 'CoreFoundation', 'AppKit', 'Carbon', 'IOKit', 'QuartzCore'])
|
||||
deps += frameworks_dep
|
||||
endif
|
||||
|
||||
|
@ -402,7 +418,10 @@ subdir('libaegisub')
|
|||
subdir('packages')
|
||||
subdir('po')
|
||||
subdir('src')
|
||||
subdir('tests')
|
||||
|
||||
if not meson.is_cross_build()
|
||||
subdir('tests')
|
||||
endif
|
||||
|
||||
aegisub_cpp_pch = ['src/include/agi_pre.h']
|
||||
aegisub_c_pch = ['src/include/agi_pre_c.h']
|
||||
|
@ -415,7 +434,7 @@ if host_machine.system() == 'windows'
|
|||
link_depends += manifest_file
|
||||
endif
|
||||
|
||||
aegisub = executable('aegisub', aegisub_src, version_h, acconf,
|
||||
aegisub = executable('aegisub', aegisub_src, version_h, acconf, resrc,
|
||||
link_with: [libresrc, libluabins, libaegisub],
|
||||
link_args: link_args,
|
||||
link_depends: link_depends,
|
||||
|
|
|
@ -28,3 +28,4 @@ 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('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')
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
<?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>
|
|
@ -1,58 +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>
|
||||
<launchable type="desktop-id">aegisub.desktop</launchable>
|
||||
<kudos>
|
||||
<kudo>HiDpiIcon</kudo>
|
||||
<kudo>HighContrast</kudo>
|
||||
<kudo>UserDocs</kudo>
|
||||
</kudos>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<_caption>Typesetting</_caption>
|
||||
<image>http://static.aegisub.org/img/screenshots/unix/typesetting-efc51b7a.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<_caption>Audio video</_caption>
|
||||
<image>http://static.aegisub.org/img/screenshots/unix/audio-video-f1f81fc2.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<_caption>Audio timing</_caption>
|
||||
<image>http://static.aegisub.org/img/screenshots/unix/audio-timing-1d8fce7e.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<developer_name>Aegisub Group</developer_name>
|
||||
<url type="bugtracker">https://github.com/Aegisub/Aegisub/issues</url>
|
||||
<url type="faq">http://docs.aegisub.org/manual/FAQ</url>
|
||||
<url type="help">http://docs.aegisub.org</url>
|
||||
<url type="homepage">http://www.aegisub.org</url>
|
||||
<url type="translate">https://sites.google.com/site/rockytdrontransifex/aegisub</url>
|
||||
<content_rating type="oars-1.0">
|
||||
<content_attribute id="social-info">mild</content_attribute>
|
||||
</content_rating>
|
||||
<translation type="gettext">aegisub</translation>
|
||||
<provides>
|
||||
<binary>aegisub</binary>
|
||||
</provides>
|
||||
<releases>
|
||||
<release version="3.2.2" date="2014-12-08"/>
|
||||
</releases>
|
||||
</component>
|
|
@ -9,7 +9,7 @@ TryExec=@AEGISUB_COMMAND@
|
|||
Icon=aegisub
|
||||
Terminal=false
|
||||
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;
|
||||
StartupNotify=true
|
||||
StartupWMClass=aegisub
|
|
@ -18,8 +18,8 @@ elif host_machine.system() == 'darwin'
|
|||
else
|
||||
conf_pkg.set('AEGISUB_COMMAND', 'aegisub')
|
||||
|
||||
desktop_template = configure_file(input: 'desktop/aegisub.desktop.template.in',
|
||||
output: 'aegisub.desktop.template',
|
||||
desktop_template = configure_file(input: 'desktop/aegisub.desktop.in.in',
|
||||
output: 'aegisub.desktop.in',
|
||||
configuration: conf_pkg)
|
||||
|
||||
i18n = import('i18n')
|
||||
|
@ -30,6 +30,16 @@ else
|
|||
install: true,
|
||||
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']
|
||||
|
||||
foreach s: aegisub_logos
|
||||
|
@ -38,4 +48,10 @@ else
|
|||
install_data('desktop' / dir / 'aegisub.' + ext,
|
||||
install_dir: datadir / 'icons' / 'hicolor' / dir / 'apps')
|
||||
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
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
[Files]
|
||||
; Avisynth
|
||||
DestDir: {app}; Source: {#DEPS_DIR}\AvisynthPlus64\x64\system\DevIL.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\plugins\DirectShowSource.dll; Flags: ignoreversion; Components: main
|
||||
DestDir: {app}; Source: {#DEPS_DIR}\AvisynthPlus64\x64\Output\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\Output\plugins\DirectShowSource.dll; Flags: ignoreversion; Components: main
|
||||
; VSFilter
|
||||
DestDir: {app}\csri; Source: {#DEPS_DIR}\VSFilter\x64\VSFilter.dll; Flags: ignoreversion; Components: main
|
||||
; VapourSynth
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
[Files]
|
||||
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]
|
||||
Filename: {tmp}\VC_redist.x{#ARCH}.exe; StatusMsg: {cm:InstallRuntime}; Parameters: "/install /quiet /norestart"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
; This file declares all installables related to spell checking and thesaurii in Aegisub
|
||||
|
||||
[Files]
|
||||
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: ignoreversion; Components: dictionaries/en_US
|
||||
Source: {#DEPS_DIR}\dictionaries\en_US.aff; DestDir: {app}\dictionaries; Flags: skipifsourcedoesntexist ignoreversion; Components: dictionaries/en_US
|
||||
Source: {#DEPS_DIR}\dictionaries\en_US.dic; DestDir: {app}\dictionaries; Flags: skipifsourcedoesntexist ignoreversion; Components: dictionaries/en_US
|
||||
|
|
|
@ -56,9 +56,9 @@ Copy-New-Item $InstallerDepsDir\dictionaries\en_US.aff $PortableOutputDir\dicti
|
|||
Copy-New-Item $InstallerDepsDir\dictionaries\en_US.dic $PortableOutputDir\dictionaries
|
||||
Write-Output 'Copying - codecs'
|
||||
Write-Output 'Copying - codecs\Avisynth'
|
||||
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\system\DevIL.dll $PortableOutputDir
|
||||
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\AviSynth.dll $PortableOutputDir
|
||||
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\plugins\DirectShowSource.dll $PortableOutputDir
|
||||
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\Output\system\DevIL.dll $PortableOutputDir
|
||||
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\Output\AviSynth.dll $PortableOutputDir
|
||||
Copy-New-Item $InstallerDepsDir\AvisynthPlus64\x64\Output\plugins\DirectShowSource.dll $PortableOutputDir
|
||||
Write-Output 'Copying - codecs\VapourSynth'
|
||||
Copy-New-Item $InstallerDepsDir\L-SMASH-Works\libvslsmashsource.dll $PortableOutputDir\vapoursynth
|
||||
Copy-New-Item $InstallerDepsDir\bestaudiosource\win64\BestAudioSource.dll $PortableOutputDir\vapoursynth
|
||||
|
@ -68,6 +68,9 @@ Write-Output 'Copying - codecs\VSFilter'
|
|||
Copy-New-Item $InstallerDepsDir\VSFilter\x64\VSFilter.dll $PortableOutputDir\csri
|
||||
Write-Output 'Copying - runtimes\MS-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'
|
||||
Copy-New-Items "$InstallerDir\share\aegisub\automation\*" "$PortableOutputDir\automation\" -Recurse
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
packages/desktop/aegisub.desktop.in.in
|
||||
packages/desktop/aegisub.appdata.xml.in.in
|
||||
|
||||
src/ass_style.cpp
|
||||
src/audio_box.cpp
|
||||
src/audio_karaoke.cpp
|
||||
|
|
11898
po/aegisub.pot
11410
po/fr_FR.po
|
@ -1,19 +1,22 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
maybe_append() {
|
||||
while read -r msg; do
|
||||
msgfile=$(echo $msg | cut -d'|' -f1)
|
||||
msgline=$(echo $msg | cut -d'|' -f2)
|
||||
msgid=$(echo $msg | cut -d'|' -f3-)
|
||||
msgfile=$(printf '%s' "$msg" | cut -d'|' -f1)
|
||||
msgline=$(printf '%s' "$msg" | cut -d'|' -f2)
|
||||
msgid=$(printf '%s' "$msg" | cut -d'|' -f3-)
|
||||
|
||||
if ! grep -Fq "msgid $msgid" aegisub.pot; then
|
||||
echo "\n#: $msgfile:$msgline\nmsgid $msgid\nmsgstr \"\"\n" >> aegisub.pot
|
||||
printf "\n#: %s:%s\nmsgid %s\nmsgstr \"\"\n\n" \
|
||||
"$msgfile" "$msgline" "$msgid" >> aegisub.pot
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
find ../src ../src/command -name \*.cpp -o -name \*.h \
|
||||
| xgettext --files-from=- -o - --c++ -k_ -kSTR_MENU -kSTR_DISP -kSTR_HELP -kfmt_tl -kfmt_plural:2,3 -kwxT -kwxPLURAL:1,2 \
|
||||
find ../src ../src/command -name '*.cpp' -o -name '*.h' \
|
||||
| xgettext --files-from=- -o - --c++ --sort-by-file \
|
||||
-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/YEAR/2005-2014/' \
|
||||
| sed "s/THE PACKAGE'S COPYRIGHT HOLDER/Rodrigo Braz Monteiro, Niels Martin Hansen, Thomas Goyne et. al./" \
|
||||
|
@ -33,28 +36,27 @@ grep '"[A-Za-z ]\+" : {' -n ../src/libresrc/default_hotkey.json \
|
|||
| sed 's/^\([0-9]\+:\).*\("[^"]\+"\).*$/default_hotkey.json|\1|\2/' \
|
||||
| maybe_append
|
||||
|
||||
find ../automation -name *.lua \
|
||||
| xargs grep tr\"[^\"]\*\" -o -n \
|
||||
find ../automation -name '*.lua' \
|
||||
| LC_ALL=C sort \
|
||||
| xargs grep 'tr"[^"]*"' -o -n \
|
||||
| sed 's/\(.*\):\([0-9]\+\):tr\(".*"\)/\1|\2|\3/' \
|
||||
| sed 's/\\/\\\\\\\\/g' \
|
||||
| maybe_append
|
||||
|
||||
for i in 'Name' 'GenericName' 'Comment' 'Keywords'
|
||||
do
|
||||
grep ^_$i -n ../packages/desktop/aegisub.desktop.template.in \
|
||||
| sed 's/\([0-9]\+\):[^=]\+=\(.*\)$/aegisub.desktop|\1|"\2"/' \
|
||||
| maybe_append
|
||||
done
|
||||
xgettext ../packages/desktop/aegisub.desktop.in.in \
|
||||
--language=Desktop --join-existing --omit-header -o aegisub.pot
|
||||
|
||||
if which xmlstarlet >/dev/null 2>&1 && which jq >/dev/null 2>&1; then
|
||||
for i in 'name' 'summary' 'p' 'li' 'caption'; do
|
||||
xmlstarlet sel -t -v "//_$i" ../packages/desktop/aegisub.appdata.xml.template.in | jq -R .
|
||||
done | nl -v0 -w1 -s'|' | sed -re 's/^/aegisub.appdata.xml|/' | maybe_append
|
||||
fi
|
||||
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
|
||||
echo "$line" \
|
||||
printf '%s\n' "$line" \
|
||||
| sed 's/[^=]*=\(.*\)/packages\/win_installer\/fragment_strings.iss|1|"\1"/' \
|
||||
| maybe_append
|
||||
done
|
||||
|
||||
for lang in $(cat LINGUAS) ; do
|
||||
# If using gettext < 0.21, run twice to avoid reversing order of old strings
|
||||
# ref: https://savannah.gnu.org/bugs/?58778
|
||||
msgmerge --update --backup=none --no-fuzzy-matching --sort-by-file "$lang".po aegisub.pot
|
||||
done
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
i18n = import('i18n')
|
||||
# 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',
|
||||
args: [
|
||||
'-k_', '-kSTR_MENU', '-kSTR_DISP', '-kSTR_HELP', '-kwxT',
|
||||
'-kfmt_tl', '-kfmt_plural:2,3', '-kwxPLURAL:1,2',
|
||||
'--sort-by-file'
|
||||
],
|
||||
install_dir: localedir)
|
||||
|
|
12432
po/pt_BR.po
13277
po/pt_PT.po
13315
po/sr_RS.po
13283
po/sr_RS@latin.po
11441
po/uk_UA.po
12396
po/zh_CN.po
12389
po/zh_TW.po
|
@ -242,7 +242,7 @@ uint32_t AssFile::AddExtradata(std::string const& key, std::string const& value)
|
|||
return data.id;
|
||||
}
|
||||
}
|
||||
Extradata.push_back(ExtradataEntry{next_extradata_id, key, value});
|
||||
Extradata.push_back(ExtradataEntry{next_extradata_id, 0, key, value});
|
||||
return next_extradata_id++; // return old value, then post-increment
|
||||
}
|
||||
|
||||
|
@ -340,10 +340,16 @@ 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()) {
|
||||
// Erase all no-longer-used extradata entries
|
||||
Extradata.erase(std::remove_if(begin(Extradata), end(Extradata), [&](ExtradataEntry const& e) {
|
||||
return !ids_used.count(e.id);
|
||||
return e.expiration_counter >= 10;
|
||||
}), end(Extradata));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,17 +50,11 @@ using EntryList = typename boost::intrusive::make_list<T, boost::intrusive::cons
|
|||
|
||||
struct ExtradataEntry {
|
||||
uint32_t id;
|
||||
int expiration_counter;
|
||||
std::string key;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
// Both start and end are inclusive
|
||||
struct LineFold {
|
||||
int start;
|
||||
int end;
|
||||
bool collapsed;
|
||||
};
|
||||
|
||||
struct AssFileCommit {
|
||||
wxString const& message;
|
||||
int *commit_id;
|
||||
|
|
|
@ -219,7 +219,7 @@ void AssParser::ParseExtradataLine(std::string const &data) {
|
|||
|
||||
// ensure next_extradata_id is always at least 1 more than the largest existing id
|
||||
target->next_extradata_id = std::max(id+1, target->next_extradata_id);
|
||||
target->Extradata.push_back(ExtradataEntry{id, std::move(key), std::move(value)});
|
||||
target->Extradata.push_back(ExtradataEntry{id, 0, std::move(key), std::move(value)});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ AudioBox::AudioBox(wxWindow *parent, agi::Context *context)
|
|||
, controller(context->audioController.get())
|
||||
, context(context)
|
||||
, audio_open_connection(context->audioController->AddAudioPlayerOpenListener(&AudioBox::OnAudioOpen, this))
|
||||
, panel(new wxPanel(this, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxBORDER_RAISED))
|
||||
, panel(new wxPanel(this, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | (OPT_GET("App/Dark Mode")->GetBool() ? wxBORDER_SIMPLE : wxBORDER_RAISED)))
|
||||
, 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))
|
||||
, VerticalZoom(new wxSlider(panel, Audio_Vertical_Zoom, OPT_GET("Audio/Zoom/Vertical")->GetInt(), 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_BOTH|wxSL_INVERSE))
|
||||
|
|
|
@ -1045,6 +1045,16 @@ void AudioDisplay::OnMouseLeave(wxMouseEvent&)
|
|||
|
||||
void AudioDisplay::OnMouseEvent(wxMouseEvent& event)
|
||||
{
|
||||
// wx doesn’t throttle for us, updating the video view is
|
||||
// very expensive, and aegisub’s work queue handling is bad,
|
||||
// so limit mouse event rate to ~200 Hz
|
||||
long ts = event.GetTimestamp();
|
||||
if (!event.IsButton() && (ts - last_event) < 5) {
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
last_event = ts;
|
||||
|
||||
// If we have focus, we get mouse move events on Mac even when the mouse is
|
||||
// outside our client rectangle, we don't want those.
|
||||
if (event.Moving() && !GetClientRect().Contains(event.GetPosition()))
|
||||
|
|
|
@ -130,6 +130,9 @@ class AudioDisplay: public wxWindow {
|
|||
/// Zoom level given as a number, see SetZoomLevel for details
|
||||
int zoom_level;
|
||||
|
||||
/// Time since last mouse event
|
||||
long last_event = 0;
|
||||
|
||||
/// Absolute pixel position of the tracking cursor (mouse or playback)
|
||||
int track_cursor_pos = -1;
|
||||
/// Label to show by track cursor
|
||||
|
|
|
@ -56,7 +56,7 @@ BSAudioProvider::BSAudioProvider(agi::fs::path const& filename, agi::BackgroundR
|
|||
{
|
||||
bs.SetMaxCacheSize(OPT_GET("Provider/Audio/BestSource/Max Cache Size")->GetInt() << 20);
|
||||
br->Run([&](agi::ProgressSink *ps) {
|
||||
ps->SetTitle(from_wx(_("Exacting")));
|
||||
ps->SetTitle(from_wx(_("Indexing")));
|
||||
ps->SetMessage(from_wx(_("Creating cache... This can take a while!")));
|
||||
ps->SetIndeterminate();
|
||||
if (bs.GetExactDuration()) {
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
|
||||
#include "audio_provider_factory.h"
|
||||
|
||||
#include "compat.h"
|
||||
#include "factory_manager.h"
|
||||
#include "options.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <libaegisub/audio/provider.h>
|
||||
#include <libaegisub/format.h>
|
||||
#include <libaegisub/fs.h>
|
||||
#include <libaegisub/log.h>
|
||||
#include <libaegisub/path.h>
|
||||
|
@ -39,6 +41,7 @@ struct factory {
|
|||
const char *name;
|
||||
std::unique_ptr<AudioProvider> (*create)(fs::path const&, BackgroundRunner *);
|
||||
bool hidden;
|
||||
std::function<bool(agi::fs::path const&)> wants_to_open = [](auto p) { return false; };
|
||||
};
|
||||
|
||||
const factory providers[] = {
|
||||
|
@ -48,13 +51,13 @@ const factory providers[] = {
|
|||
{"FFmpegSource", CreateFFmpegSourceAudioProvider, false},
|
||||
#endif
|
||||
#ifdef WITH_AVISYNTH
|
||||
{"Avisynth", CreateAvisynthAudioProvider, false},
|
||||
{"Avisynth", CreateAvisynthAudioProvider, false, [](auto p) { return agi::fs::HasExtension(p, "avs"); }},
|
||||
#endif
|
||||
#ifdef WITH_BESTSOURCE
|
||||
{"BestSource", CreateBSAudioProvider, false},
|
||||
#endif
|
||||
#ifdef WITH_VAPOURSYNTH
|
||||
{"VapourSynth", CreateVapourSynthAudioProvider, false},
|
||||
{"VapourSynth", CreateVapourSynthAudioProvider, false, [](auto p) { return agi::fs::HasExtension(p, "py") || agi::fs::HasExtension(p, "vpy"); }},
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
@ -63,51 +66,83 @@ std::vector<std::string> GetAudioProviderNames() {
|
|||
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,
|
||||
Path const& path_helper,
|
||||
BackgroundRunner *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);
|
||||
}
|
||||
std::unique_ptr<agi::AudioProvider> provider = SelectAudioProvider(filename, path_helper, br);
|
||||
|
||||
bool needs_cache = provider->NeedsCache();
|
||||
|
||||
|
|
|
@ -58,7 +58,11 @@ VapourSynthAudioProvider::VapourSynthAudioProvider(agi::fs::path const& filename
|
|||
|
||||
VSCleanCache();
|
||||
|
||||
script = vs.GetScriptAPI()->createScript(nullptr);
|
||||
VSCore *core = vs.GetAPI()->createCore(OPT_GET("Provider/VapourSynth/Autoload User Plugins")->GetBool() ? 0 : VSCoreCreationFlags::ccfDisableAutoLoading);
|
||||
if (core == nullptr) {
|
||||
throw VapourSynthError("Error creating core");
|
||||
}
|
||||
script = vs.GetScriptAPI()->createScript(core);
|
||||
if (script == nullptr) {
|
||||
throw VapourSynthError("Error creating script API");
|
||||
}
|
||||
|
@ -86,9 +90,7 @@ VapourSynthAudioProvider::VapourSynthAudioProvider(agi::fs::path const& filename
|
|||
num_samples = vi->numSamples;
|
||||
}
|
||||
catch (VapourSynthError const& err) {
|
||||
// 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()));
|
||||
throw agi::AudioProviderError(err.GetMessage());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
|
|
@ -216,6 +216,7 @@ namespace Automation4 {
|
|||
wxWindow *ww = config_dialog->CreateWindow(&w); // generate actual dialog contents
|
||||
s->Add(ww, 0, wxALL, 5); // add contents to dialog
|
||||
w.SetSizerAndFit(s);
|
||||
w.SetLayoutAdaptationMode(wxDIALOG_ADAPTATION_MODE_ENABLED);
|
||||
w.CenterOnParent();
|
||||
w.ShowModal();
|
||||
});
|
||||
|
@ -315,11 +316,14 @@ namespace Automation4 {
|
|||
|
||||
std::vector<std::future<std::unique_ptr<Script>>> script_futures;
|
||||
|
||||
auto path_it = agi::Split(path, '|');
|
||||
for (auto tok : std::set<agi::StringRange>(begin(path_it), end(path_it))) {
|
||||
std::set<agi::fs::path> dirnames;
|
||||
for (auto tok : agi::Split(path, '|')) {
|
||||
auto dirname = config::path->Decode(agi::str(tok));
|
||||
if (!agi::fs::DirectoryExists(dirname)) continue;
|
||||
|
||||
if (dirnames.count(dirname)) continue;
|
||||
dirnames.insert(dirname);
|
||||
|
||||
for (auto filename : agi::fs::DirectoryIterator(dirname, "*.*"))
|
||||
script_futures.emplace_back(std::async(std::launch::async, [=] {
|
||||
return ScriptFactory::CreateFromFile(dirname/filename, false, false);
|
||||
|
|
|
@ -677,6 +677,7 @@ namespace {
|
|||
if (lua_isnumber(L, -1) && lua_tointeger(L, -1) == 3) {
|
||||
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.";
|
||||
stackcheck.check_stack(0);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -689,6 +690,7 @@ namespace {
|
|||
name = GetPrettyFilename().string();
|
||||
|
||||
lua_pop(L, 1);
|
||||
stackcheck.check_stack(0);
|
||||
// if we got this far, the script should be ready
|
||||
loaded = true;
|
||||
}
|
||||
|
|
|
@ -78,6 +78,8 @@ namespace Automation4 {
|
|||
std::deque<PendingCommit> pending_commits;
|
||||
/// Lines to delete once processing complete successfully
|
||||
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
|
||||
/// hasn't already happened. This is done lazily, since it only needs
|
||||
|
@ -118,6 +120,8 @@ namespace Automation4 {
|
|||
/// 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);
|
||||
|
||||
std::unique_ptr<AssEntry> LuaToTrackedAssEntry(lua_State *L);
|
||||
|
||||
/// @brief Signal that the script using this file is now done running
|
||||
/// @param set_undo If there's any uncommitted changes to the file,
|
||||
/// they will be automatically committed with this
|
||||
|
|
|
@ -328,6 +328,12 @@ namespace Automation4 {
|
|||
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)
|
||||
{
|
||||
switch (lua_type(L, 2)) {
|
||||
|
@ -453,7 +459,7 @@ namespace Automation4 {
|
|||
// insert
|
||||
CheckBounds(n);
|
||||
|
||||
auto e = LuaToAssEntry(L, ass);
|
||||
auto e = LuaToTrackedAssEntry(L);
|
||||
modification_type |= modification_mask(e.get());
|
||||
QueueLineForDeletion(n - 1);
|
||||
AssignLine(n - 1, std::move(e));
|
||||
|
@ -542,7 +548,7 @@ namespace Automation4 {
|
|||
|
||||
for (int i = 1; i <= n; i++) {
|
||||
lua_pushvalue(L, i);
|
||||
auto e = LuaToAssEntry(L, ass);
|
||||
auto e = LuaToTrackedAssEntry(L);
|
||||
modification_type |= modification_mask(e.get());
|
||||
|
||||
if (lines.empty()) {
|
||||
|
@ -586,7 +592,7 @@ namespace Automation4 {
|
|||
new_entries.reserve(n - 1);
|
||||
for (int i = 2; i <= n; i++) {
|
||||
lua_pushvalue(L, i);
|
||||
auto e = LuaToAssEntry(L, ass);
|
||||
auto e = LuaToTrackedAssEntry(L);
|
||||
modification_type |= modification_mask(e.get());
|
||||
InsertLine(new_entries, i - 2, std::move(e));
|
||||
lua_pop(L, 1);
|
||||
|
@ -734,6 +740,7 @@ namespace Automation4 {
|
|||
void LuaAssFile::Cancel()
|
||||
{
|
||||
for (auto& line : lines_to_delete) line.release();
|
||||
for (AssEntry *line : allocated_lines) delete line;
|
||||
references--;
|
||||
if (!references) delete this;
|
||||
}
|
||||
|
|
|
@ -326,6 +326,12 @@ namespace Automation4 {
|
|||
{
|
||||
lua_getfield(L, -1, "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; }
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
// Allocate storage for and initialise static members
|
||||
namespace {
|
||||
int avs_refcount = 0;
|
||||
bool failed = false;
|
||||
#ifdef _WIN32
|
||||
HINSTANCE hLib = nullptr;
|
||||
#else
|
||||
|
@ -66,8 +67,8 @@ const AVS_Linkage *AVS_linkage = nullptr;
|
|||
|
||||
typedef IScriptEnvironment* __stdcall FUNC(int);
|
||||
|
||||
AviSynthWrapper::AviSynthWrapper() {
|
||||
if (!avs_refcount){
|
||||
AviSynthWrapper::AviSynthWrapper() try {
|
||||
if (!avs_refcount++) {
|
||||
#ifdef _WIN32
|
||||
#define CONCATENATE(x, y) x ## y
|
||||
#define _Lstr(x) CONCATENATE(L, x)
|
||||
|
@ -94,8 +95,6 @@ AviSynthWrapper::AviSynthWrapper() {
|
|||
if (!env)
|
||||
throw AvisynthError("Failed to create a new avisynth script environment. Avisynth is too old?");
|
||||
|
||||
avs_refcount++;
|
||||
|
||||
AVS_linkage = env->GetAVSLinkage();
|
||||
|
||||
// Set memory limit
|
||||
|
@ -103,6 +102,9 @@ AviSynthWrapper::AviSynthWrapper() {
|
|||
if (memoryMax)
|
||||
env->SetMemoryMax(memoryMax);
|
||||
}
|
||||
} catch (AvisynthError const&) {
|
||||
avs_refcount--;
|
||||
throw;
|
||||
}
|
||||
|
||||
AviSynthWrapper::~AviSynthWrapper() {
|
||||
|
|
|
@ -56,13 +56,14 @@
|
|||
#include <wx/scrolbar.h>
|
||||
#include <wx/sizer.h>
|
||||
|
||||
// Check menu.h for id range allocation before editing this enum
|
||||
enum {
|
||||
GRID_SCROLLBAR = 1730,
|
||||
MENU_SHOW_COL = 1250 // Needs 15 IDs after this
|
||||
MENU_SHOW_COL = (wxID_HIGHEST + 1) + 2000 // Needs 15 IDs after this
|
||||
};
|
||||
|
||||
BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context)
|
||||
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxSUNKEN_BORDER)
|
||||
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | (OPT_GET("App/Dark Mode")->GetBool() ? wxBORDER_SIMPLE : wxSUNKEN_BORDER))
|
||||
, scrollBar(new wxScrollBar(this, GRID_SCROLLBAR, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL))
|
||||
, context(context)
|
||||
, columns(GetGridColumns())
|
||||
|
@ -579,7 +580,7 @@ void BaseGrid::OnMouseEvent(wxMouseEvent &event) {
|
|||
void BaseGrid::OnContextMenu(wxContextMenuEvent &evt) {
|
||||
wxPoint pos = evt.GetPosition();
|
||||
if (pos == wxDefaultPosition || ScreenToClient(pos).y > lineHeight) {
|
||||
if (!context_menu) context_menu = menu::GetMenu("grid_context", context);
|
||||
if (!context_menu) context_menu = menu::GetMenu("grid_context", (wxID_HIGHEST + 1) + 8000, context);
|
||||
menu::OpenPopupMenu(context_menu.get(), this);
|
||||
}
|
||||
else {
|
||||
|
|
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 416 B |
Before Width: | Height: | Size: 501 B After Width: | Height: | Size: 533 B |
Before Width: | Height: | Size: 567 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 153 B After Width: | Height: | Size: 210 B |
Before Width: | Height: | Size: 208 B After Width: | Height: | Size: 232 B |
Before Width: | Height: | Size: 194 B After Width: | Height: | Size: 265 B |
Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 416 B |
Before Width: | Height: | Size: 418 B After Width: | Height: | Size: 538 B |
Before Width: | Height: | Size: 162 B After Width: | Height: | Size: 280 B |
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 335 B |
Before Width: | Height: | Size: 189 B After Width: | Height: | Size: 339 B |
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 512 B |
Before Width: | Height: | Size: 413 B After Width: | Height: | Size: 677 B |
Before Width: | Height: | Size: 271 B After Width: | Height: | Size: 323 B |
Before Width: | Height: | Size: 395 B After Width: | Height: | Size: 363 B |
Before Width: | Height: | Size: 353 B After Width: | Height: | Size: 370 B |
Before Width: | Height: | Size: 808 B After Width: | Height: | Size: 565 B |
Before Width: | Height: | Size: 939 B After Width: | Height: | Size: 612 B |
Before Width: | Height: | Size: 155 B After Width: | Height: | Size: 209 B |
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 223 B |
Before Width: | Height: | Size: 210 B After Width: | Height: | Size: 259 B |
Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 415 B |
Before Width: | Height: | Size: 440 B After Width: | Height: | Size: 532 B |
Before Width: | Height: | Size: 166 B After Width: | Height: | Size: 279 B |
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 345 B |
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 390 B |