diff --git a/README.md b/README.md index 405cada7f7..8bcd14acc5 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ We absolutely do, and I'm aware that adding another one [doesn't sound like](htt - [AegisubDC](https://github.com/Ristellise/AegisubDC) has the most modern features (in particular video-panning), but is Windows-only and not actively maintained anymore. - [The TypesettingTools fork](https://github.com/TypesettingTools/Aegisub) is the one that will one day become the upstream version and builds relatively effortlessly on all operating systems, but at the moment it's not moving much. It's the base for this fork, and I hope to one day merge most of these additions into it. - Only PR'ing the changes in here to various forks would cause even more chaos -- I try to convince myself that this isn't really a "fork" in the traditional sense - one which aims to provide extended support and stability fixes. It's a collection of new feature additions which I built myself, together with some of the most important new features floating around other forks. +- ~~I try to convince myself that this isn't really a "fork" in the traditional sense - one which aims to provide extended support and stability fixes. It's a collection of new feature additions which I built myself, together with some of the most important new features floating around other forks.~~ At this point it's probably too late to still be saying this. Still, the general mission hasn't changed. This fork collects new features and critical bugfixes, but won't be putting extra time into maintenance aspects like cleanup and refactors. Partly, this is also because any big refactors would make it harder to pull these changes into upstream repositories or future forks. - While this is usually also the version of Aegisub I'm currently using, I make absolutely no promises on stability. **Don't** use this version if you're just looking for any version of Aegisub - this is mostly intended for typesetting and other advanced usage. + While this is usually also the version of Aegisub I'm currently using, I make no promises on stability. **Don't** use this version if you're just looking for any version of Aegisub - this is mostly intended for typesetting and other advanced usage. ### Organization Being a collection of different feature additions, this repository consists of a set of branches for different features, so that they can easily be merged into other repositories. The [`feature`](https://github.com/arch1t3cht/Aegisub/tree/feature) branch merges together all the features I deem as currently usable. Due to the structure of the repository, I will be force-pushing to this branch and some of the individual branches very frequently, so they're not ideal for basing further branches on. @@ -31,10 +31,11 @@ This list is for navigating the repository. Go to the [release page](https://git - [`workarounds`](https://github.com/arch1t3cht/Aegisub/tree/workarounds): Same as `bugfixes`, but these are hacky fixes that probably shouldn't be pulled without more work. - [`fixes`](https://github.com/arch1t3cht/Aegisub/tree/fixes): Miscellaneous bugfixes - [`misc`](https://github.com/arch1t3cht/Aegisub/tree/misc): Other miscellaneous additions +- [`wangqr_gui`](https://github.com/arch1t3cht/Aegisub/tree/wangqr_gui): Merge wangqr's changes regarding the GUI. In particular, add high-DPI compatibility. - [`misc_dc`](https://github.com/arch1t3cht/Aegisub/tree/misc_dc): Miscellaneous changes taken from AegisubDC - [`xa2-ds`](https://github.com/arch1t3cht/Aegisub/tree/xa2-ds): Add XAudio2 backend and allow stereo playback for some other backends, by wangqr and Shinon. - [`stereo`](https://github.com/arch1t3cht/Aegisub/tree/stereo): Add multi-channel support for the other audio backends where possible. -- [`video_panning_feature`](https://github.com/arch1t3cht/Aegisub/tree/video_panning_feature): Merge [moex3's video zoom and panning](https://github.com/TypesettingTools/Aegisub/pull/150), with an OSX fix and more options to control zoom behavior +- [`video_panning_option`](https://github.com/arch1t3cht/Aegisub/tree/video_panning_option): Merge [moex3's video zoom and panning](https://github.com/TypesettingTools/Aegisub/pull/150), with several bugfixes and more options to control zoom behavior - [`spectrum-frequency-mapping`](https://github.com/arch1t3cht/Aegisub/tree/spectrum-frequency-mapping): Merge EleonoreMizo's [spectrum display improvements](https://github.com/TypesettingTools/Aegisub/pull/94), and also make Shift+Scroll vertically zoom the audio display - [`wangqr_time_video`](https://github.com/arch1t3cht/Aegisub/tree/wangqr_time_video): Merge wangqr's feature adding a tool for timing subtitles to changes in the video @@ -64,11 +65,13 @@ If you're compiling yourself, try adding `--force-fallback-for=zlib` to the meso ### Compilation 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 Linux, you can use the [TSTools PKGBUILD](https://aur.archlinux.org/packages/aegisub-ttools-meson-git) as a base, in particular for installing the necessary dependencies if you don't want to compile them yourself. -To compile manually, -- Install Meson (at the moment, you'll need to downgrade Meson below 0.63.0: `pip install meson==0.62.2`) +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. +If all dependencies are installed: +- Install Meson - Clone the repository -- In the repository, run `meson setup build` for the default configuration. See below for further options. +- In the repository, run `meson setup build --buildtype=release` for the default configuration. See below for further options. - `cd` to the `build` directory and run `ninja` - You'll get an `aegisub` binary in the `build` folder. To install it to a system-wide location, run `ninja install`. To install to `/usr` instead of `/usr/local`, pass `--prefix=/usr` when configuring or reconfiguring meson. - When recompiling after pulling new commits, skip the `meson setup` setup and just immediately run `ninja` from the build directory - even when the build configuration changed. diff --git a/libaegisub/ass/dialogue_parser.cpp b/libaegisub/ass/dialogue_parser.cpp index ea486dcf71..8cd1743e7b 100644 --- a/libaegisub/ass/dialogue_parser.cpp +++ b/libaegisub/ass/dialogue_parser.cpp @@ -60,7 +60,11 @@ class SyntaxHighlighter { case dt::ERROR: SetStyling(tok.length, ss::ERROR); break; case dt::ARG: SetStyling(tok.length, ss::PARAMETER); break; case dt::COMMENT: SetStyling(tok.length, ss::COMMENT); break; - case dt::DRAWING: SetStyling(tok.length, ss::DRAWING); break; + case dt::DRAWING_CMD:SetStyling(tok.length, ss::DRAWING_CMD);break; + case dt::DRAWING_X: SetStyling(tok.length, ss::DRAWING_X); break; + case dt::DRAWING_Y: SetStyling(tok.length, ss::DRAWING_Y); break; + case dt::DRAWING_ENDPOINT_X: SetStyling(tok.length, ss::DRAWING_ENDPOINT_X); break; + case dt::DRAWING_ENDPOINT_Y: SetStyling(tok.length, ss::DRAWING_ENDPOINT_Y); break; case dt::TEXT: SetStyling(tok.length, ss::NORMAL); break; case dt::TAG_NAME: SetStyling(tok.length, ss::TAG); break; case dt::OPEN_PAREN: case dt::CLOSE_PAREN: case dt::ARG_SEP: case dt::TAG_START: @@ -72,6 +76,8 @@ class SyntaxHighlighter { case dt::WHITESPACE: if (ranges.size() && ranges.back().type == ss::PARAMETER) SetStyling(tok.length, ss::PARAMETER); + else if (ranges.size() && ranges.back().type == ss::DRAWING_ENDPOINT_X) + SetStyling(tok.length, ss::DRAWING_ENDPOINT_X); // connect the underline between x and y of endpoints else SetStyling(tok.length, ss::NORMAL); break; @@ -118,6 +124,64 @@ class WordSplitter { } } + void SplitDrawing(size_t &i) { + size_t starti = i; + + // First, split into words + size_t dpos = pos; + size_t tlen = 0; + bool tokentype = text[pos] == ' ' || text[pos] == '\t'; + while (tlen < tokens[i].length) { + bool newtype = text[dpos] == ' ' || text[dpos] == '\t'; + if (newtype != tokentype) { + tokentype = newtype; + SwitchTo(i, tokentype ? dt::DRAWING_FULL : dt::WHITESPACE, tlen); + tokens[i].type = tokentype ? dt::WHITESPACE : dt::DRAWING_FULL; + tlen = 0; + } + ++tlen; + ++dpos; + } + + // Then, label all the tokens + dpos = pos; + int num_coord = 0; + char lastcmd = ' '; + + for (size_t j = starti; j <= i; j++) { + char c = text[dpos]; + if (tokens[j].type == dt::WHITESPACE) { + } else if (c == 'm' || c == 'n' || c == 'l' || c == 's' || c == 'b' || c == 'p' || c == 'c') { + tokens[j].type = dt::DRAWING_CMD; + + if (tokens[j].length != 1) + tokens[j].type = dt::ERROR; + if (num_coord % 2 != 0) + tokens[j].type = dt::ERROR; + + lastcmd = c; + num_coord = 0; + } else { + bool valid = true; + for (size_t k = 0; k < tokens[j].length; k++) { + char c = text[dpos + k]; + if (!((c >= '0' && c <= '9') || c == '.' || c == '-' || c == 'e')) { + valid = false; + } + } + if (!valid) + tokens[j].type = dt::ERROR; + else if (lastcmd == 'b' && num_coord % 6 >= 4) + tokens[j].type = num_coord % 2 == 0 ? dt::DRAWING_ENDPOINT_X : dt::DRAWING_ENDPOINT_Y; + else + tokens[j].type = num_coord % 2 == 0 ? dt::DRAWING_X : dt::DRAWING_Y; + ++num_coord; + } + + dpos += tokens[j].length; + } + } + public: WordSplitter(std::string const& text, std::vector &tokens) : text(text) @@ -131,6 +195,9 @@ class WordSplitter { size_t len = tokens[i].length; if (tokens[i].type == dt::TEXT) SplitText(i); + else if (tokens[i].type == dt::DRAWING_FULL) { + SplitDrawing(i); + } pos += len; } } @@ -163,9 +230,51 @@ void MarkDrawings(std::string const& str, std::vector &tokens) { switch (tokens[i].type) { case dt::TEXT: if (in_drawing) - tokens[i].type = dt::DRAWING; + tokens[i].type = dt::DRAWING_FULL; break; case dt::TAG_NAME: + if (i + 3 < tokens.size() && (len == 4 || len == 5) && !strncmp(str.c_str() + pos + len - 4, "clip", 4)) { + if (tokens[i + 1].type != dt::OPEN_PAREN) + goto tag_p; + + size_t drawing_start = 0; + size_t drawing_end = 0; + + // Try to find a vector clip + for (size_t j = i + 2; j < tokens.size(); j++) { + if (tokens[j].type == dt::ARG_SEP) { + if (drawing_start) { + break; // More than two arguents - this is a rectangular clip + } + drawing_start = j + 1; + } else if (tokens[j].type == dt::CLOSE_PAREN) { + drawing_end = j; + break; + } else if (tokens[j].type != dt::WHITESPACE && tokens[j].type != dt::ARG) { + break; + } + } + + if (!drawing_end) + goto tag_p; + if (!drawing_start) + drawing_start = i + 2; + if (drawing_end == drawing_start + 1) + goto tag_p; + + // We found a clip between drawing_start and drawing_end. Now, join + // all the tokens into one and label it as a drawing. + size_t tokenlen = 0; + for (size_t j = drawing_start; j < drawing_end; j++) { + tokenlen += tokens[j].length; + } + + tokens[drawing_start].length = tokenlen; + tokens[drawing_start].type = dt::DRAWING_FULL; + tokens.erase(tokens.begin() + drawing_start + 1, tokens.begin() + drawing_end); + last_ovr_end -= drawing_end - drawing_start - 1; + } +tag_p: if (len != 1 || i + 1 >= tokens.size() || str[pos] != 'p') break; @@ -199,7 +308,7 @@ void MarkDrawings(std::string const& str, std::vector &tokens) { case dt::KARAOKE_VARIABLE: break; case dt::LINE_BREAK: break; default: - tokens[i].type = in_drawing ? dt::DRAWING : dt::TEXT; + tokens[i].type = in_drawing ? dt::DRAWING_FULL : dt::TEXT; if (i > 0 && tokens[i - 1].type == tokens[i].type) { tokens[i - 1].length += tokens[i].length; tokens.erase(tokens.begin() + i); diff --git a/libaegisub/common/vfr.cpp b/libaegisub/common/vfr.cpp index c82fcdbeed..377e29b34e 100644 --- a/libaegisub/common/vfr.cpp +++ b/libaegisub/common/vfr.cpp @@ -225,7 +225,7 @@ int Framerate::FrameAtTime(int ms, Time type) const { return int((ms * numerator / denominator - 999) / 1000); if (ms > timecodes.back()) - return int((ms * numerator - last + denominator - 1) / denominator / 1000) + (int)timecodes.size() - 1; + return int((ms * numerator - numerator / 2 - last + numerator - 1) / denominator / 1000) + (int)timecodes.size() - 1; return (int)distance(lower_bound(timecodes.rbegin(), timecodes.rend(), ms, std::greater()), timecodes.rend()) - 1; } diff --git a/libaegisub/include/libaegisub/ass/dialogue_parser.h b/libaegisub/include/libaegisub/ass/dialogue_parser.h index 0aa3f9962b..727c0569f9 100644 --- a/libaegisub/include/libaegisub/ass/dialogue_parser.h +++ b/libaegisub/include/libaegisub/ass/dialogue_parser.h @@ -39,7 +39,12 @@ namespace agi { ERROR, COMMENT, WHITESPACE, - DRAWING, + DRAWING_FULL, + DRAWING_CMD, + DRAWING_X, + DRAWING_Y, + DRAWING_ENDPOINT_X, + DRAWING_ENDPOINT_Y, KARAOKE_TEMPLATE, KARAOKE_VARIABLE }; @@ -49,7 +54,11 @@ namespace agi { enum { NORMAL = 0, COMMENT, - DRAWING, + DRAWING_CMD, + DRAWING_X, + DRAWING_Y, + DRAWING_ENDPOINT_X, + DRAWING_ENDPOINT_Y, OVERRIDE, PUNCTUATION, TAG, diff --git a/src/audio_provider_vs.cpp b/src/audio_provider_vs.cpp index 575daaad6c..b41b467732 100644 --- a/src/audio_provider_vs.cpp +++ b/src/audio_provider_vs.cpp @@ -84,7 +84,9 @@ VapoursynthAudioProvider::VapoursynthAudioProvider(agi::fs::path const& filename num_samples = vi->numSamples; } catch (VapoursynthError const& err) { - throw agi::AudioProviderError(agi::format("Vapoursynth error: %s", 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 @@ -115,7 +117,7 @@ void VapoursynthAudioProvider::FillBufferWithFrame(void *buf, int n, int64_t sta std::vector planes(channels); for (int c = 0; c < channels; c++) { - planes[c] = vs.GetAPI()->getReadPtr(frame, c); + planes[c] = vs.GetAPI()->getReadPtr(frame, c) + bytes_per_sample * start; if (planes[c] == nullptr) { vs.GetAPI()->freeFrame(frame); throw VapoursynthError("Failed to read audio channel"); diff --git a/src/libresrc/default_config.json b/src/libresrc/default_config.json index 38175b16ab..928de309e4 100644 --- a/src/libresrc/default_config.json +++ b/src/libresrc/default_config.json @@ -231,7 +231,9 @@ "Background" : { "Brackets" : "", "Comment" : "", - "Drawing" : "", + "Drawing Command" : "", + "Drawing X" : "", + "Drawing Y" : "", "Error" : "rgb(255, 200, 200)", "Karaoke Template" : "", "Karaoke Variable" : "", @@ -244,7 +246,9 @@ "Bold" : { "Brackets" : false, "Comment" : true, - "Drawing" : true, + "Drawing Command" : true, + "Drawing X" : false, + "Drawing Y" : false, "Error" : false, "Karaoke Template" : true, "Karaoke Variable" : true, @@ -254,9 +258,14 @@ "Slashes" : false, "Tags" : true }, + "Underline": { + "Drawing Endpoint": true + }, "Brackets" : "rgb(20, 50, 255)", "Comment" : "rgb(0,0,0)", - "Drawing" : "rgb(0,0,0)", + "Drawing Command" : "rgb(0,0,0)", + "Drawing X" : "rgb(90,40,40)", + "Drawing Y" : "rgb(40,90,40)", "Error" : "rgb(200, 0, 0)", "Karaoke Template" : "rgb(128, 0, 192)", "Karaoke Variable" : "rgb(128, 0, 192)", diff --git a/src/libresrc/osx/default_config.json b/src/libresrc/osx/default_config.json index 6c7a301fbd..328464e931 100644 --- a/src/libresrc/osx/default_config.json +++ b/src/libresrc/osx/default_config.json @@ -231,7 +231,9 @@ "Background" : { "Brackets" : "", "Comment" : "", - "Drawing" : "", + "Drawing Command" : "", + "Drawing X" : "", + "Drawing Y" : "", "Error" : "rgb(255, 200, 200)", "Karaoke Template" : "", "Karaoke Variable" : "", @@ -244,7 +246,9 @@ "Bold" : { "Brackets" : false, "Comment" : true, - "Drawing" : true, + "Drawing Command" : true, + "Drawing X" : false, + "Drawing Y" : false, "Error" : false, "Karaoke Template" : true, "Karaoke Variable" : true, @@ -254,9 +258,14 @@ "Slashes" : false, "Tags" : true }, + "Underline": { + "Drawing Endpoint": true + }, "Brackets" : "rgb(20, 50, 255)", "Comment" : "rgb(0,0,0)", - "Drawing" : "rgb(0,0,0)", + "Drawing Command" : "rgb(0,0,0)", + "Drawing X" : "rgb(90,40,40)", + "Drawing Y" : "rgb(40,90,40)", "Error" : "rgb(200, 0, 0)", "Karaoke Template" : "rgb(128, 0, 192)", "Karaoke Variable" : "rgb(128, 0, 192)", diff --git a/src/preferences.cpp b/src/preferences.cpp index d855034b71..c35fa99a6c 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -259,7 +259,11 @@ void Interface_Colours(wxTreebook *book, Preferences *parent) { p->OptionAdd(syntax, _("Background"), "Colour/Subtitle/Background"); p->OptionAdd(syntax, _("Normal"), "Colour/Subtitle/Syntax/Normal"); p->OptionAdd(syntax, _("Comments"), "Colour/Subtitle/Syntax/Comment"); - p->OptionAdd(syntax, _("Drawings"), "Colour/Subtitle/Syntax/Drawing"); + p->OptionAdd(syntax, _("Drawing Commands"), "Colour/Subtitle/Syntax/Drawing Command"); + p->OptionAdd(syntax, _("Drawing X Coords"), "Colour/Subtitle/Syntax/Drawing X"); + p->OptionAdd(syntax, _("Drawing Y Coords"), "Colour/Subtitle/Syntax/Drawing Y"); + p->OptionAdd(syntax, _("Underline Spline Endpoints"), "Colour/Subtitle/Syntax/Underline/Drawing Endpoint"); + p->CellSkip(syntax); p->OptionAdd(syntax, _("Brackets"), "Colour/Subtitle/Syntax/Brackets"); p->OptionAdd(syntax, _("Slashes and Parentheses"), "Colour/Subtitle/Syntax/Slashes"); p->OptionAdd(syntax, _("Tags"), "Colour/Subtitle/Syntax/Tags"); diff --git a/src/res/aegisub.exe.manifest b/src/res/aegisub.exe.manifest index 5867c4a130..9ea55ca4b9 100644 --- a/src/res/aegisub.exe.manifest +++ b/src/res/aegisub.exe.manifest @@ -1,6 +1,6 @@ - Aegisub subtitle editor + Aegisub Subtitle Editor diff --git a/src/subs_edit_ctrl.cpp b/src/subs_edit_ctrl.cpp index 8edc9b561f..ee5389a489 100644 --- a/src/subs_edit_ctrl.cpp +++ b/src/subs_edit_ctrl.cpp @@ -138,7 +138,10 @@ SubsTextEditCtrl::SubsTextEditCtrl(wxWindow* parent, wxSize wsize, long style, a OPT_SUB("Subtitle/Edit Box/Font Size", &SubsTextEditCtrl::SetStyles, this); Subscribe("Normal"); Subscribe("Comment"); - Subscribe("Drawing"); + Subscribe("Drawing Command"); + Subscribe("Drawing X"); + Subscribe("Drawing Y"); + OPT_SUB("Colour/Subtitle/Syntax/Underline/Drawing Endpoint", &SubsTextEditCtrl::SetStyles, this); Subscribe("Brackets"); Subscribe("Slashes"); Subscribe("Tags"); @@ -230,7 +233,13 @@ void SubsTextEditCtrl::SetStyles() { namespace ss = agi::ass::SyntaxStyle; SetSyntaxStyle(ss::NORMAL, font, "Normal", default_background); SetSyntaxStyle(ss::COMMENT, font, "Comment", default_background); - SetSyntaxStyle(ss::DRAWING, font, "Drawing", default_background); + SetSyntaxStyle(ss::DRAWING_CMD, font, "Drawing Command", default_background); + SetSyntaxStyle(ss::DRAWING_X, font, "Drawing X", default_background); + SetSyntaxStyle(ss::DRAWING_Y, font, "Drawing Y", default_background); + SetSyntaxStyle(ss::DRAWING_ENDPOINT_X, font, "Drawing X", default_background); + SetSyntaxStyle(ss::DRAWING_ENDPOINT_Y, font, "Drawing Y", default_background); + StyleSetUnderline(ss::DRAWING_ENDPOINT_X, OPT_GET("Colour/Subtitle/Syntax/Underline/Drawing Endpoint")->GetBool()); + StyleSetUnderline(ss::DRAWING_ENDPOINT_Y, OPT_GET("Colour/Subtitle/Syntax/Underline/Drawing Endpoint")->GetBool()); SetSyntaxStyle(ss::OVERRIDE, font, "Brackets", default_background); SetSyntaxStyle(ss::PUNCTUATION, font, "Slashes", default_background); SetSyntaxStyle(ss::TAG, font, "Tags", default_background); diff --git a/src/vapoursynth_common.cpp b/src/vapoursynth_common.cpp index 617849360d..52614528c0 100644 --- a/src/vapoursynth_common.cpp +++ b/src/vapoursynth_common.cpp @@ -23,15 +23,17 @@ #include int OpenScriptOrVideo(const VSSCRIPTAPI *api, VSScript *script, agi::fs::path const& filename, std::string default_script) { + int result; if (agi::fs::HasExtension(filename, "py") || agi::fs::HasExtension(filename, "vpy")) { - return api->evaluateFile(script, filename.string().c_str()); + result = api->evaluateFile(script, filename.string().c_str()); } else { std::string fname = filename.string(); boost::replace_all(fname, "\\", "\\\\"); boost::replace_all(fname, "'", "\\'"); std::string vscript = "filename = '" + fname + "'\n" + default_script; - return api->evaluateBuffer(script, vscript.c_str(), "aegisub"); + result = api->evaluateBuffer(script, vscript.c_str(), "aegisub"); } + return result; } #endif // WITH_VAPOURSYNTH diff --git a/src/vapoursynth_wrap.cpp b/src/vapoursynth_wrap.cpp index d0d0fbd8b0..ac2b87dffb 100644 --- a/src/vapoursynth_wrap.cpp +++ b/src/vapoursynth_wrap.cpp @@ -78,7 +78,12 @@ VapourSynthWrapper::VapourSynthWrapper() { if (!getVSScriptAPI) throw VapoursynthError("Failed to get address of getVSScriptAPI from " VSSCRIPT_SO); + // Python will set the program's locale to the user's default locale, which will break + // half of wxwidgets on some operating systems due to locale mismatches. There's not really anything + // we can do to fix it except for saving it and setting it back to its original value afterwards. + std::string oldlocale(setlocale(LC_ALL, NULL)); scriptapi = getVSScriptAPI(VSSCRIPT_API_VERSION); + setlocale(LC_ALL, oldlocale.c_str()); if (!scriptapi) throw VapoursynthError("Failed to get Vapoursynth ScriptAPI"); diff --git a/src/video_display.cpp b/src/video_display.cpp index 4857df0349..4087f74a07 100644 --- a/src/video_display.cpp +++ b/src/video_display.cpp @@ -201,7 +201,7 @@ void VideoDisplay::Render() try { E(glMatrixMode(GL_PROJECTION)); E(glLoadIdentity()); - E(glOrtho(0.0f, client_w, client_h, 0.0f, -1000.0f, 1000.0f)); + E(glOrtho(0.0f, std::max(client_w, 1), std::max(client_h, 1), 0.0f, -1000.0f, 1000.0f)); if (OPT_GET("Video/Overscan Mask")->GetBool()) { double ar = con->videoController->GetAspectRatioValue(); @@ -233,19 +233,19 @@ catch (const agi::Exception &err) { } void VideoDisplay::DrawOverscanMask(float horizontal_percent, float vertical_percent) const { - Vector2D v(viewport_width, viewport_height); - Vector2D size = Vector2D(horizontal_percent, vertical_percent) / 2 * v; + Vector2D v = Vector2D(viewport_width, viewport_height) / scale_factor; + Vector2D size = Vector2D(horizontal_percent, vertical_percent) * v; // Clockwise from top-left Vector2D corners[] = { size, - Vector2D(viewport_width - size.X(), size), + Vector2D(viewport_width / scale_factor - size.X(), size), v - size, - Vector2D(size, viewport_height - size.Y()) + Vector2D(size, viewport_height / scale_factor - size.Y()) }; // Shift to compensate for black bars - Vector2D pos(viewport_left, viewport_top); + Vector2D pos = Vector2D(viewport_left, viewport_top) / scale_factor; for (auto& corner : corners) corner = corner + pos; @@ -267,7 +267,7 @@ void VideoDisplay::DrawOverscanMask(float horizontal_percent, float vertical_per std::vector vstart(1, 0); std::vector vcount(1, count); - gl.DrawMultiPolygon(points, vstart, vcount, Vector2D(viewport_left, viewport_top), Vector2D(viewport_width, viewport_height), true); + gl.DrawMultiPolygon(points, vstart, vcount, pos, v, true); } void VideoDisplay::PositionVideo() { @@ -360,7 +360,7 @@ void VideoDisplay::OnSizeEvent(wxSizeEvent &event) { /* If the video is moving, we only need to update the size in this case */ else if (videoSize.GetWidth() == 0 && videoSize.GetHeight() == 0) videoSize = GetClientSize() * videoZoomValue * scale_factor; - windowZoomValue = double(std::max(GetClientSize().GetHeight(), 1) * scale_factor) / con->project->VideoProvider()->GetHeight(); + windowZoomValue = double(GetClientSize().GetHeight() * scale_factor) / con->project->VideoProvider()->GetHeight(); zoomBox->ChangeValue(fmt_wx("%g%%", windowZoomValue * 100.)); con->ass->Properties.video_zoom = windowZoomValue; UpdateSize(); diff --git a/src/video_provider_vs.cpp b/src/video_provider_vs.cpp index a0a006f436..8e110eb3fc 100644 --- a/src/video_provider_vs.cpp +++ b/src/video_provider_vs.cpp @@ -63,11 +63,12 @@ class VapoursynthVideoProvider: public VideoProvider { int GetHeight() const override { return vi->height; } double GetDAR() const override { return dar; } std::vector GetKeyFrames() const override { return keyframes; } - std::string GetColorSpace() const override { return colorspace; } - std::string GetRealColorSpace() const override { return colorspace; } + std::string GetColorSpace() const override { return GetRealColorSpace(); } + std::string GetRealColorSpace() const override { return colorspace == "Unknown" ? "None" : colorspace; } bool HasAudio() const override { return false; } - virtual bool WantsCaching() const override { return true; } - virtual std::string GetDecoderName() const override { return "VapourSynth"; } + bool WantsCaching() const override { return true; } + std::string GetDecoderName() const override { return "VapourSynth"; } + bool ShouldSetVideoProperties() const override { return colorspace != "Unknown"; } }; std::string colormatrix_description(int colorFamily, int colorRange, int matrix) { @@ -90,7 +91,7 @@ std::string colormatrix_description(int colorFamily, int colorRange, int matrix) case VSC_MATRIX_ST240_M: return str + ".240M"; default: - return "None"; + return "Unknown"; // Will return "None" in GetColorSpace } } @@ -137,7 +138,7 @@ VapoursynthVideoProvider::VapoursynthVideoProvider(agi::fs::path const& filename // Assume constant frame rate, since handling VFR would require going through all frames when loading. // Users can load custom timecodes files to deal with VFR. // Alternatively (TODO) the provider could read timecodes and keyframes from a second output node. - fps = (double) vi->fpsNum / vi->fpsDen; + fps = agi::vfr::Framerate(vi->fpsNum, vi->fpsDen); // Find the first frame to get some info const VSFrame *frame; diff --git a/tests/tests/syntax_highlight.cpp b/tests/tests/syntax_highlight.cpp index ea3a2cc63e..6b10c10b8e 100644 --- a/tests/tests/syntax_highlight.cpp +++ b/tests/tests/syntax_highlight.cpp @@ -74,14 +74,47 @@ TEST(lagi_syntax, spellcheck) { } TEST(lagi_syntax, drawing) { - tok_str("incorrect{\\p1}m 10 10{\\p}correct", false, + tok_str("incorrect{\\clip(m 10 10 l 20 20 c)\\p1}m 10 10 b 0 0 0 100 100 0{\\p}correct", false, expect_style(ss::SPELLING, 9u); expect_style(ss::OVERRIDE, 1u); expect_style(ss::PUNCTUATION, 1u); + expect_style(ss::TAG, 4u); + expect_style(ss::PUNCTUATION, 1u); + expect_style(ss::DRAWING_CMD, 1u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_X, 2u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_Y, 2u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_CMD, 1u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_X, 2u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_Y, 2u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_CMD, 1u); + expect_style(ss::PUNCTUATION, 2u); expect_style(ss::TAG, 1u); expect_style(ss::PARAMETER, 1u); expect_style(ss::OVERRIDE, 1u); - expect_style(ss::DRAWING, 7u); + expect_style(ss::DRAWING_CMD, 1u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_X, 2u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_Y, 2u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_CMD, 1u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_X, 1u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_Y, 1u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_X, 1u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_Y, 3u); + expect_style(ss::NORMAL, 1u); + expect_style(ss::DRAWING_ENDPOINT_X, 4u); + expect_style(ss::DRAWING_ENDPOINT_Y, 1u); expect_style(ss::OVERRIDE, 1u); expect_style(ss::PUNCTUATION, 1u); expect_style(ss::TAG, 1u); diff --git a/tests/tests/vfr.cpp b/tests/tests/vfr.cpp index a9b8f3acf4..90a02d4c6d 100644 --- a/tests/tests/vfr.cpp +++ b/tests/tests/vfr.cpp @@ -149,6 +149,12 @@ TEST(lagi_vfr, cfr_round_trip_exact) { for (int i = -10; i < 11; i++) { EXPECT_EQ(i, fps.FrameAtTime(fps.TimeAtFrame(i))); } + + ASSERT_NO_THROW(fps = Framerate(24000, 1001)); + int frames[] = {-100, -10, -1, 0, 1, 10, 100, 6820}; + for (int i : frames) { + EXPECT_EQ(i, fps.FrameAtTime(fps.TimeAtFrame(i))); + } } TEST(lagi_vfr, cfr_round_trip_start) { @@ -157,6 +163,12 @@ TEST(lagi_vfr, cfr_round_trip_start) { for (int i = -10; i < 11; i++) { EXPECT_EQ(i, fps.FrameAtTime(fps.TimeAtFrame(i, START), START)); } + + ASSERT_NO_THROW(fps = Framerate(24000, 1001)); + int frames[] = {-100, -10, -1, 0, 1, 10, 100, 6820}; + for (int i : frames) { + EXPECT_EQ(i, fps.FrameAtTime(fps.TimeAtFrame(i, START), START)); + } } TEST(lagi_vfr, cfr_round_trip_end) { @@ -165,6 +177,12 @@ TEST(lagi_vfr, cfr_round_trip_end) { for (int i = -10; i < 11; i++) { EXPECT_EQ(i, fps.FrameAtTime(fps.TimeAtFrame(i, END), END)); } + + ASSERT_NO_THROW(fps = Framerate(24000, 1001)); + int frames[] = {-100, -10, -1, 0, 1, 10, 100, 6820}; + for (int i : frames) { + EXPECT_EQ(i, fps.FrameAtTime(fps.TimeAtFrame(i, END), END)); + } } TEST(lagi_vfr, vfr_round_trip_exact) { diff --git a/tests/tests/word_split.cpp b/tests/tests/word_split.cpp index 00ba82fcd1..8ed0ff83fc 100644 --- a/tests/tests/word_split.cpp +++ b/tests/tests/word_split.cpp @@ -108,12 +108,12 @@ TEST(lagi_word_split, drawing) { SplitWords(text, tokens); - ASSERT_EQ(15u, tokens.size()); + ASSERT_EQ(17u, tokens.size()); EXPECT_EQ(dt::WORD, tokens[0].type); EXPECT_EQ(dt::WORD, tokens[2].type); - EXPECT_EQ(dt::WORD, tokens[14].type); + EXPECT_EQ(dt::WORD, tokens[16].type); - EXPECT_EQ(dt::DRAWING, tokens[8].type); + EXPECT_EQ(dt::DRAWING_CMD, tokens[8].type); } TEST(lagi_word_split, unclosed_ovr) {