Merge branches 'vapoursynth' and 'info' into feature

This commit is contained in:
arch1t3cht 2023-06-11 01:22:14 +02:00
commit 9e8ac83998
8 changed files with 67 additions and 24 deletions

View file

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

View file

@ -28,6 +28,8 @@ other data.
import os
import os.path
import re
from enum import Enum
from tkinter.messagebox import askyesno
from collections import deque
from typing import Any, Dict, List, Tuple
@ -216,25 +218,36 @@ 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 get_keyframes(filename: str, clip: vs.VideoNode, fallback: str | List[int],
generate: GenKeyframesMode = GenKeyframesMode.ASK, **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.
"""
kffilename = make_keyframes_filename(filename)
if not os.path.exists(kffilename):
if generate == GenKeyframesMode.NEVER:
return fallback
if generate == GenKeyframesMode.ASK and not 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"):
return fallback
vs.core.log_message(vs.MESSAGE_TYPE_INFORMATION, "No keyframes file found, detecting keyframes...\n")
keyframes = make_keyframes(clip, **kwargs)
save_keyframes(kffilename, keyframes)

View file

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

View file

@ -392,7 +392,7 @@
},
"VapourSynth" : {
"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 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"
"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 `bas` plugin.\n__aegi_hasaudio = 1 if a.check_audio(filename) else 0"
}
}
},

View file

@ -392,7 +392,7 @@
},
"VapourSynth" : {
"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 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"
"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 `bas` plugin.\n__aegi_hasaudio = 1 if a.check_audio(filename) else 0"
}
}
},

View file

@ -501,22 +501,36 @@ void VapourSynth(wxTreebook *book, Preferences *parent) {
auto video = p->PageSizer(_("Default Video Script"));
auto make_default_button = [=](std::string optname, wxTextCtrl *ctrl) {
auto showdefault = new wxButton(p, -1, _("Set to Default"));
showdefault->Bind(wxEVT_BUTTON, [=](auto e) {
ctrl->SetValue(OPT_GET(optname)->GetDefaultString());
});
return showdefault;
};
auto vhint = new wxStaticText(p, wxID_ANY, _("This script will be executed to load video files that aren't\nVapourSynth scripts (i.e. end in .py or .vpy).\nThe filename variable stores the path to the file."));
p->sizer->Fit(p);
vhint->Wrap(400);
video->Add(vhint, 0, wxALL, 5);
video->AddSpacer(16);
p->CellSkip(video);
p->OptionAddMultiline(video, "Provider/Video/VapourSynth/Default Script");
auto vdef = p->OptionAddMultiline(video, "Provider/Video/VapourSynth/Default Script");
p->CellSkip(video);
video->Add(make_default_button("Provider/Video/VapourSynth/Default Script", vdef), wxSizerFlags().Right());
auto audio = p->PageSizer(_("Default Audio Script"));
auto ahint = new wxStaticText(p, wxID_ANY, _("This script will be executed to load audio files that aren't\nVapourSynth scripts (i.e. end in .py or .vpy).\nThe filename variable stores the path to the file."));
p->sizer->Fit(p);
ahint->Wrap(400);
audio->Add(ahint, 0, wxALL, 5);
audio->AddSpacer(16);
p->CellSkip(audio);
p->OptionAddMultiline(audio, "Provider/Audio/VapourSynth/Default Script");
auto adef = p->OptionAddMultiline(audio, "Provider/Audio/VapourSynth/Default Script");
p->CellSkip(audio);
audio->Add(make_default_button("Provider/Audio/VapourSynth/Default Script", adef), wxSizerFlags().Right());
p->SetSizerAndFit(p->sizer);
#endif

View file

@ -156,7 +156,7 @@ wxControl *OptionPage::OptionAdd(wxFlexGridSizer *flex, const wxString &name, co
}
}
wxControl *OptionPage::OptionAddMultiline(wxSizer *sizer, const char *opt_name) {
wxTextCtrl *OptionPage::OptionAddMultiline(wxSizer *sizer, const char *opt_name) {
parent->AddChangeableOption(opt_name);
const auto opt = OPT_GET(opt_name);

View file

@ -43,7 +43,7 @@ public:
void CellSkip(wxFlexGridSizer *flex);
wxControl *OptionAdd(wxFlexGridSizer *flex, const wxString &name, const char *opt_name, double min=0, double max=INT_MAX, double inc=1);
wxControl *OptionAddMultiline(wxSizer *flex, const char *opt_name);
wxTextCtrl *OptionAddMultiline(wxSizer *flex, const char *opt_name);
void OptionChoice(wxFlexGridSizer *flex, const wxString &name, const wxArrayString &choices, const char *opt_name);
void OptionBrowse(wxFlexGridSizer *flex, const wxString &name, const char *opt_name, wxControl *enabler = nullptr, bool do_enable = false);
void OptionFont(wxSizer *sizer, std::string opt_prefix);