diff --git a/README.md b/README.md index 405cada7f..8bcd14acc 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 ea486dcf7..8cd1743e7 100644 --- a/libaegisub/ass/dialogue_parser.cpp +++ b/libaegisub/ass/dialogue_parser.cpp @@ -60,7 +60,11 @@ public: 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 @@ public: 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 @@ public: 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 c82fcdbee..377e29b34 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 0aa3f9962..727c0569f 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 575daaad6..b41b46773 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 38175b16a..928de309e 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 6c7a301fb..328464e93 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 d855034b7..c35fa99a6 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 5867c4a13..9ea55ca4b 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 8edc9b561..ee5389a48 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 617849360..52614528c 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 d0d0fbd8b..ac2b87dff 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 e2492819c..4087f74a0 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() { diff --git a/src/video_provider_vs.cpp b/src/video_provider_vs.cpp index a0a006f43..8e110eb3f 100644 --- a/src/video_provider_vs.cpp +++ b/src/video_provider_vs.cpp @@ -63,11 +63,12 @@ public: 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 ea3a2cc63..6b10c10b8 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 a9b8f3acf..90a02d4c6 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 00ba82fcd..8ed0ff83f 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) {