this repo has no description
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

🚧 Less I/O

authored by

Gwenn Le Bihan and committed by
Gwen Le Bihan
35451db9 b302acc0

+714 -581
+512 -3
Cargo.lock
··· 48 48 ] 49 49 50 50 [[package]] 51 + name = "anstream" 52 + version = "0.6.15" 53 + source = "registry+https://github.com/rust-lang/crates.io-index" 54 + checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 55 + dependencies = [ 56 + "anstyle", 57 + "anstyle-parse", 58 + "anstyle-query", 59 + "anstyle-wincon", 60 + "colorchoice", 61 + "is_terminal_polyfill", 62 + "utf8parse", 63 + ] 64 + 65 + [[package]] 66 + name = "anstyle" 67 + version = "1.0.10" 68 + source = "registry+https://github.com/rust-lang/crates.io-index" 69 + checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 70 + 71 + [[package]] 72 + name = "anstyle-parse" 73 + version = "0.2.6" 74 + source = "registry+https://github.com/rust-lang/crates.io-index" 75 + checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 76 + dependencies = [ 77 + "utf8parse", 78 + ] 79 + 80 + [[package]] 81 + name = "anstyle-query" 82 + version = "1.1.1" 83 + source = "registry+https://github.com/rust-lang/crates.io-index" 84 + checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 85 + dependencies = [ 86 + "windows-sys", 87 + ] 88 + 89 + [[package]] 90 + name = "anstyle-wincon" 91 + version = "3.0.4" 92 + source = "registry+https://github.com/rust-lang/crates.io-index" 93 + checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 94 + dependencies = [ 95 + "anstyle", 96 + "windows-sys", 97 + ] 98 + 99 + [[package]] 51 100 name = "anyhow" 52 101 version = "1.0.82" 53 102 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 58 107 version = "1.0.1" 59 108 source = "registry+https://github.com/rust-lang/crates.io-index" 60 109 checksum = "170433209e817da6aae2c51aa0dd443009a613425dd041ebfb2492d1c4c11a25" 110 + 111 + [[package]] 112 + name = "arrayref" 113 + version = "0.3.9" 114 + source = "registry+https://github.com/rust-lang/crates.io-index" 115 + checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" 116 + 117 + [[package]] 118 + name = "arrayvec" 119 + version = "0.7.6" 120 + source = "registry+https://github.com/rust-lang/crates.io-index" 121 + checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 61 122 62 123 [[package]] 63 124 name = "ascii" ··· 152 213 checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 153 214 154 215 [[package]] 216 + name = "bytemuck" 217 + version = "1.21.0" 218 + source = "registry+https://github.com/rust-lang/crates.io-index" 219 + checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" 220 + 221 + [[package]] 222 + name = "byteorder-lite" 223 + version = "0.1.0" 224 + source = "registry+https://github.com/rust-lang/crates.io-index" 225 + checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" 226 + 227 + [[package]] 155 228 name = "bytes" 156 229 version = "1.10.0" 157 230 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 239 312 source = "git+https://github.com/robbert-vdh/clap-sys.git?branch=feature%2Fcstr-macro#523a5f8a8dd021ec99e7d6e0c0ebe7741a3da9d4" 240 313 241 314 [[package]] 315 + name = "color_quant" 316 + version = "1.1.0" 317 + source = "registry+https://github.com/rust-lang/crates.io-index" 318 + checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 319 + 320 + [[package]] 321 + name = "colorchoice" 322 + version = "1.0.3" 323 + source = "registry+https://github.com/rust-lang/crates.io-index" 324 + checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 325 + 326 + [[package]] 242 327 name = "console" 243 328 version = "0.15.8" 244 329 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 266 351 version = "0.8.6" 267 352 source = "registry+https://github.com/rust-lang/crates.io-index" 268 353 checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 354 + 355 + [[package]] 356 + name = "core_maths" 357 + version = "0.1.1" 358 + source = "registry+https://github.com/rust-lang/crates.io-index" 359 + checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" 360 + dependencies = [ 361 + "libm", 362 + ] 269 363 270 364 [[package]] 271 365 name = "cpufeatures" ··· 352 446 ] 353 447 354 448 [[package]] 449 + name = "data-url" 450 + version = "0.3.1" 451 + source = "registry+https://github.com/rust-lang/crates.io-index" 452 + checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" 453 + 454 + [[package]] 355 455 name = "deranged" 356 456 version = "0.3.11" 357 457 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 401 501 checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 402 502 403 503 [[package]] 504 + name = "env_filter" 505 + version = "0.1.3" 506 + source = "registry+https://github.com/rust-lang/crates.io-index" 507 + checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 508 + dependencies = [ 509 + "log", 510 + "regex", 511 + ] 512 + 513 + [[package]] 514 + name = "env_logger" 515 + version = "0.11.6" 516 + source = "registry+https://github.com/rust-lang/crates.io-index" 517 + checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" 518 + dependencies = [ 519 + "anstream", 520 + "anstyle", 521 + "env_filter", 522 + "humantime", 523 + "log", 524 + ] 525 + 526 + [[package]] 404 527 name = "equivalent" 405 528 version = "1.0.2" 406 529 source = "registry+https://github.com/rust-lang/crates.io-index" 407 530 checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 408 531 409 532 [[package]] 533 + name = "fdeflate" 534 + version = "0.3.7" 535 + source = "registry+https://github.com/rust-lang/crates.io-index" 536 + checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" 537 + dependencies = [ 538 + "simd-adler32", 539 + ] 540 + 541 + [[package]] 410 542 name = "flate2" 411 543 version = "1.0.35" 412 544 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 415 547 "crc32fast", 416 548 "miniz_oxide 0.8.4", 417 549 ] 550 + 551 + [[package]] 552 + name = "float-cmp" 553 + version = "0.9.0" 554 + source = "registry+https://github.com/rust-lang/crates.io-index" 555 + checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" 418 556 419 557 [[package]] 420 558 name = "fnv" ··· 423 561 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 424 562 425 563 [[package]] 564 + name = "fontconfig-parser" 565 + version = "0.5.7" 566 + source = "registry+https://github.com/rust-lang/crates.io-index" 567 + checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" 568 + dependencies = [ 569 + "roxmltree 0.20.0", 570 + ] 571 + 572 + [[package]] 573 + name = "fontdb" 574 + version = "0.22.0" 575 + source = "registry+https://github.com/rust-lang/crates.io-index" 576 + checksum = "a3a6f9af55fb97ad673fb7a69533eb2f967648a06fa21f8c9bb2cd6d33975716" 577 + dependencies = [ 578 + "fontconfig-parser", 579 + "log", 580 + "memmap2", 581 + "slotmap", 582 + "tinyvec", 583 + "ttf-parser", 584 + ] 585 + 586 + [[package]] 426 587 name = "generic-array" 427 588 version = "0.14.7" 428 589 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 446 607 ] 447 608 448 609 [[package]] 610 + name = "gif" 611 + version = "0.13.1" 612 + source = "registry+https://github.com/rust-lang/crates.io-index" 613 + checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" 614 + dependencies = [ 615 + "color_quant", 616 + "weezl", 617 + ] 618 + 619 + [[package]] 449 620 name = "gimli" 450 621 version = "0.28.1" 451 622 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 533 704 checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 534 705 535 706 [[package]] 707 + name = "humantime" 708 + version = "2.1.0" 709 + source = "registry+https://github.com/rust-lang/crates.io-index" 710 + checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 711 + 712 + [[package]] 536 713 name = "iana-time-zone" 537 714 version = "0.1.60" 538 715 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 556 733 ] 557 734 558 735 [[package]] 736 + name = "image-webp" 737 + version = "0.1.3" 738 + source = "registry+https://github.com/rust-lang/crates.io-index" 739 + checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" 740 + dependencies = [ 741 + "byteorder-lite", 742 + "quick-error", 743 + ] 744 + 745 + [[package]] 746 + name = "imagesize" 747 + version = "0.13.0" 748 + source = "registry+https://github.com/rust-lang/crates.io-index" 749 + checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" 750 + 751 + [[package]] 559 752 name = "indexmap" 560 753 version = "2.7.1" 561 754 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 588 781 ] 589 782 590 783 [[package]] 784 + name = "is_terminal_polyfill" 785 + version = "1.70.1" 786 + source = "registry+https://github.com/rust-lang/crates.io-index" 787 + checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 788 + 789 + [[package]] 591 790 name = "itertools" 592 791 version = "0.12.1" 593 792 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 612 811 ] 613 812 614 813 [[package]] 814 + name = "kurbo" 815 + version = "0.11.1" 816 + source = "registry+https://github.com/rust-lang/crates.io-index" 817 + checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" 818 + dependencies = [ 819 + "arrayvec", 820 + "smallvec", 821 + ] 822 + 823 + [[package]] 615 824 name = "lazy_static" 616 825 version = "1.4.0" 617 826 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 624 833 checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 625 834 626 835 [[package]] 836 + name = "libm" 837 + version = "0.2.11" 838 + source = "registry+https://github.com/rust-lang/crates.io-index" 839 + checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" 840 + 841 + [[package]] 627 842 name = "lock_api" 628 843 version = "0.4.12" 629 844 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 635 850 636 851 [[package]] 637 852 name = "log" 638 - version = "0.4.25" 853 + version = "0.4.26" 639 854 source = "registry+https://github.com/rust-lang/crates.io-index" 640 - checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 855 + checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" 641 856 642 857 [[package]] 643 858 name = "malloc_buf" ··· 649 864 ] 650 865 651 866 [[package]] 867 + name = "measure_time" 868 + version = "0.9.0" 869 + source = "registry+https://github.com/rust-lang/crates.io-index" 870 + checksum = "51c55d61e72fc3ab704396c5fa16f4c184db37978ae4e94ca8959693a235fc0e" 871 + dependencies = [ 872 + "log", 873 + ] 874 + 875 + [[package]] 652 876 name = "memchr" 653 877 version = "2.7.2" 654 878 source = "registry+https://github.com/rust-lang/crates.io-index" 655 879 checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 880 + 881 + [[package]] 882 + name = "memmap2" 883 + version = "0.9.5" 884 + source = "registry+https://github.com/rust-lang/crates.io-index" 885 + checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" 886 + dependencies = [ 887 + "libc", 888 + ] 656 889 657 890 [[package]] 658 891 name = "midi-consts" ··· 685 918 checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" 686 919 dependencies = [ 687 920 "adler2", 921 + "simd-adler32", 688 922 ] 689 923 690 924 [[package]] ··· 893 1127 ] 894 1128 895 1129 [[package]] 1130 + name = "pico-args" 1131 + version = "0.5.0" 1132 + source = "registry+https://github.com/rust-lang/crates.io-index" 1133 + checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" 1134 + 1135 + [[package]] 896 1136 name = "plain" 897 1137 version = "0.2.3" 898 1138 source = "registry+https://github.com/rust-lang/crates.io-index" 899 1139 checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 1140 + 1141 + [[package]] 1142 + name = "png" 1143 + version = "0.17.16" 1144 + source = "registry+https://github.com/rust-lang/crates.io-index" 1145 + checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" 1146 + dependencies = [ 1147 + "bitflags 1.3.2", 1148 + "crc32fast", 1149 + "fdeflate", 1150 + "flate2", 1151 + "miniz_oxide 0.8.4", 1152 + ] 900 1153 901 1154 [[package]] 902 1155 name = "portable-atomic" ··· 926 1179 ] 927 1180 928 1181 [[package]] 1182 + name = "quick-error" 1183 + version = "2.0.1" 1184 + source = "registry+https://github.com/rust-lang/crates.io-index" 1185 + checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" 1186 + 1187 + [[package]] 929 1188 name = "quote" 930 1189 version = "1.0.36" 931 1190 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1038 1297 checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" 1039 1298 1040 1299 [[package]] 1300 + name = "resvg" 1301 + version = "0.44.0" 1302 + source = "registry+https://github.com/rust-lang/crates.io-index" 1303 + checksum = "4a325d5e8d1cebddd070b13f44cec8071594ab67d1012797c121f27a669b7958" 1304 + dependencies = [ 1305 + "gif", 1306 + "image-webp", 1307 + "log", 1308 + "pico-args", 1309 + "rgb", 1310 + "svgtypes", 1311 + "tiny-skia", 1312 + "usvg", 1313 + "zune-jpeg", 1314 + ] 1315 + 1316 + [[package]] 1317 + name = "rgb" 1318 + version = "0.8.50" 1319 + source = "registry+https://github.com/rust-lang/crates.io-index" 1320 + checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" 1321 + dependencies = [ 1322 + "bytemuck", 1323 + ] 1324 + 1325 + [[package]] 1041 1326 name = "ring" 1042 1327 version = "0.17.9" 1043 1328 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1058 1343 checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" 1059 1344 1060 1345 [[package]] 1346 + name = "roxmltree" 1347 + version = "0.20.0" 1348 + source = "registry+https://github.com/rust-lang/crates.io-index" 1349 + checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" 1350 + 1351 + [[package]] 1061 1352 name = "rust-analyzer" 1062 1353 version = "0.0.1" 1063 1354 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1117 1408 checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" 1118 1409 1119 1410 [[package]] 1411 + name = "rustybuzz" 1412 + version = "0.18.0" 1413 + source = "registry+https://github.com/rust-lang/crates.io-index" 1414 + checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181" 1415 + dependencies = [ 1416 + "bitflags 2.8.0", 1417 + "bytemuck", 1418 + "core_maths", 1419 + "log", 1420 + "smallvec", 1421 + "ttf-parser", 1422 + "unicode-bidi-mirroring", 1423 + "unicode-ccc", 1424 + "unicode-properties", 1425 + "unicode-script", 1426 + ] 1427 + 1428 + [[package]] 1120 1429 name = "ryu" 1121 1430 version = "1.0.17" 1122 1431 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1228 1537 "chrono-human-duration", 1229 1538 "console", 1230 1539 "docopt", 1540 + "env_logger", 1231 1541 "getrandom", 1232 1542 "handlebars", 1233 1543 "hound", 1234 1544 "indicatif", 1235 1545 "itertools", 1546 + "log", 1547 + "measure_time", 1236 1548 "midly", 1237 1549 "nanoid", 1238 1550 "nih_plug", 1239 1551 "once_cell", 1240 1552 "rand", 1241 - "roxmltree", 1553 + "resvg", 1554 + "roxmltree 0.19.0", 1242 1555 "rust-analyzer", 1243 1556 "serde", 1244 1557 "serde_cbor", ··· 1247 1560 "strum", 1248 1561 "strum_macros", 1249 1562 "svg", 1563 + "tiny-skia", 1250 1564 "tiny_http", 1251 1565 "ureq", 1252 1566 "wasm-bindgen", ··· 1260 1574 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1261 1575 1262 1576 [[package]] 1577 + name = "simd-adler32" 1578 + version = "0.3.7" 1579 + source = "registry+https://github.com/rust-lang/crates.io-index" 1580 + checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 1581 + 1582 + [[package]] 1583 + name = "simplecss" 1584 + version = "0.2.2" 1585 + source = "registry+https://github.com/rust-lang/crates.io-index" 1586 + checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" 1587 + dependencies = [ 1588 + "log", 1589 + ] 1590 + 1591 + [[package]] 1592 + name = "siphasher" 1593 + version = "1.0.1" 1594 + source = "registry+https://github.com/rust-lang/crates.io-index" 1595 + checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 1596 + 1597 + [[package]] 1598 + name = "slotmap" 1599 + version = "1.0.7" 1600 + source = "registry+https://github.com/rust-lang/crates.io-index" 1601 + checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" 1602 + dependencies = [ 1603 + "version_check", 1604 + ] 1605 + 1606 + [[package]] 1263 1607 name = "slug" 1264 1608 version = "0.1.5" 1265 1609 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1276 1620 checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 1277 1621 1278 1622 [[package]] 1623 + name = "strict-num" 1624 + version = "0.1.1" 1625 + source = "registry+https://github.com/rust-lang/crates.io-index" 1626 + checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" 1627 + dependencies = [ 1628 + "float-cmp", 1629 + ] 1630 + 1631 + [[package]] 1279 1632 name = "strsim" 1280 1633 version = "0.10.0" 1281 1634 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1314 1667 version = "0.17.0" 1315 1668 source = "registry+https://github.com/rust-lang/crates.io-index" 1316 1669 checksum = "700efb40f3f559c23c18b446e8ed62b08b56b2bb3197b36d57e0470b4102779e" 1670 + 1671 + [[package]] 1672 + name = "svgtypes" 1673 + version = "0.15.3" 1674 + source = "registry+https://github.com/rust-lang/crates.io-index" 1675 + checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" 1676 + dependencies = [ 1677 + "kurbo", 1678 + "siphasher", 1679 + ] 1317 1680 1318 1681 [[package]] 1319 1682 name = "syn" ··· 1400 1763 ] 1401 1764 1402 1765 [[package]] 1766 + name = "tiny-skia" 1767 + version = "0.11.4" 1768 + source = "registry+https://github.com/rust-lang/crates.io-index" 1769 + checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" 1770 + dependencies = [ 1771 + "arrayref", 1772 + "arrayvec", 1773 + "bytemuck", 1774 + "cfg-if", 1775 + "log", 1776 + "png", 1777 + "tiny-skia-path", 1778 + ] 1779 + 1780 + [[package]] 1781 + name = "tiny-skia-path" 1782 + version = "0.11.4" 1783 + source = "registry+https://github.com/rust-lang/crates.io-index" 1784 + checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" 1785 + dependencies = [ 1786 + "arrayref", 1787 + "bytemuck", 1788 + "strict-num", 1789 + ] 1790 + 1791 + [[package]] 1403 1792 name = "tiny_http" 1404 1793 version = "0.12.0" 1405 1794 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1412 1801 ] 1413 1802 1414 1803 [[package]] 1804 + name = "tinyvec" 1805 + version = "1.8.1" 1806 + source = "registry+https://github.com/rust-lang/crates.io-index" 1807 + checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" 1808 + dependencies = [ 1809 + "tinyvec_macros", 1810 + ] 1811 + 1812 + [[package]] 1813 + name = "tinyvec_macros" 1814 + version = "0.1.1" 1815 + source = "registry+https://github.com/rust-lang/crates.io-index" 1816 + checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1817 + 1818 + [[package]] 1415 1819 name = "toml" 1416 1820 version = "0.7.8" 1417 1821 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1443 1847 "serde_spanned", 1444 1848 "toml_datetime", 1445 1849 "winnow", 1850 + ] 1851 + 1852 + [[package]] 1853 + name = "ttf-parser" 1854 + version = "0.24.1" 1855 + source = "registry+https://github.com/rust-lang/crates.io-index" 1856 + checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" 1857 + dependencies = [ 1858 + "core_maths", 1446 1859 ] 1447 1860 1448 1861 [[package]] ··· 1458 1871 checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" 1459 1872 1460 1873 [[package]] 1874 + name = "unicode-bidi" 1875 + version = "0.3.18" 1876 + source = "registry+https://github.com/rust-lang/crates.io-index" 1877 + checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" 1878 + 1879 + [[package]] 1880 + name = "unicode-bidi-mirroring" 1881 + version = "0.3.0" 1882 + source = "registry+https://github.com/rust-lang/crates.io-index" 1883 + checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f" 1884 + 1885 + [[package]] 1886 + name = "unicode-ccc" 1887 + version = "0.3.0" 1888 + source = "registry+https://github.com/rust-lang/crates.io-index" 1889 + checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" 1890 + 1891 + [[package]] 1461 1892 name = "unicode-ident" 1462 1893 version = "1.0.12" 1463 1894 source = "registry+https://github.com/rust-lang/crates.io-index" 1464 1895 checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1465 1896 1466 1897 [[package]] 1898 + name = "unicode-properties" 1899 + version = "0.1.3" 1900 + source = "registry+https://github.com/rust-lang/crates.io-index" 1901 + checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" 1902 + 1903 + [[package]] 1904 + name = "unicode-script" 1905 + version = "0.5.7" 1906 + source = "registry+https://github.com/rust-lang/crates.io-index" 1907 + checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" 1908 + 1909 + [[package]] 1910 + name = "unicode-vo" 1911 + version = "0.1.0" 1912 + source = "registry+https://github.com/rust-lang/crates.io-index" 1913 + checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" 1914 + 1915 + [[package]] 1467 1916 name = "unicode-width" 1468 1917 version = "0.1.12" 1469 1918 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1506 1955 ] 1507 1956 1508 1957 [[package]] 1958 + name = "usvg" 1959 + version = "0.44.0" 1960 + source = "registry+https://github.com/rust-lang/crates.io-index" 1961 + checksum = "7447e703d7223b067607655e625e0dbca80822880248937da65966194c4864e6" 1962 + dependencies = [ 1963 + "base64", 1964 + "data-url", 1965 + "flate2", 1966 + "fontdb", 1967 + "imagesize", 1968 + "kurbo", 1969 + "log", 1970 + "pico-args", 1971 + "roxmltree 0.20.0", 1972 + "rustybuzz", 1973 + "simplecss", 1974 + "siphasher", 1975 + "strict-num", 1976 + "svgtypes", 1977 + "tiny-skia-path", 1978 + "unicode-bidi", 1979 + "unicode-script", 1980 + "unicode-vo", 1981 + "xmlwriter", 1982 + ] 1983 + 1984 + [[package]] 1509 1985 name = "utf-8" 1510 1986 version = "0.7.6" 1511 1987 source = "registry+https://github.com/rust-lang/crates.io-index" 1512 1988 checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1513 1989 1514 1990 [[package]] 1991 + name = "utf8parse" 1992 + version = "0.2.2" 1993 + source = "registry+https://github.com/rust-lang/crates.io-index" 1994 + checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1995 + 1996 + [[package]] 1515 1997 name = "version_check" 1516 1998 version = "0.9.4" 1517 1999 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1632 2114 dependencies = [ 1633 2115 "rustls-pki-types", 1634 2116 ] 2117 + 2118 + [[package]] 2119 + name = "weezl" 2120 + version = "0.1.8" 2121 + source = "registry+https://github.com/rust-lang/crates.io-index" 2122 + checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" 1635 2123 1636 2124 [[package]] 1637 2125 name = "widestring" ··· 1828 2316 ] 1829 2317 1830 2318 [[package]] 2319 + name = "xmlwriter" 2320 + version = "0.1.0" 2321 + source = "registry+https://github.com/rust-lang/crates.io-index" 2322 + checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" 2323 + 2324 + [[package]] 1831 2325 name = "xtask" 1832 2326 version = "0.1.0" 1833 2327 dependencies = [ ··· 1839 2333 version = "1.8.1" 1840 2334 source = "registry+https://github.com/rust-lang/crates.io-index" 1841 2335 checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2336 + 2337 + [[package]] 2338 + name = "zune-core" 2339 + version = "0.4.12" 2340 + source = "registry+https://github.com/rust-lang/crates.io-index" 2341 + checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" 2342 + 2343 + [[package]] 2344 + name = "zune-jpeg" 2345 + version = "0.4.14" 2346 + source = "registry+https://github.com/rust-lang/crates.io-index" 2347 + checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" 2348 + dependencies = [ 2349 + "zune-core", 2350 + ]
+5
Cargo.toml
··· 56 56 strum = { version = "0.26.2", features = ["strum_macros"] } 57 57 strum_macros = "0.26.2" 58 58 ureq = "3.0.6" 59 + tiny-skia = "0.11.4" 60 + resvg = "0.44.0" 61 + measure_time = "0.9.0" 62 + env_logger = "0.11.6" 63 + log = "0.4.26" 59 64 60 65 61 66 [dev-dependencies]
+2 -2
Justfile
··· 21 21 cp shapemaker ~/.local/bin/ 22 22 23 23 example-video out="out.mp4" args='': 24 - ./shapemaker video --colors colorschemes/palenight.css {{out}} --sync-with fixtures/schedule-hell.midi --audio fixtures/schedule-hell.flac --grid-size 16x10 --resolution 1920 {{args}} 24 + ./shapemaker video --colors colorschemes/palenight.css {{out}} --sync-with fixtures/schedule-hell.midi --audio fixtures/schedule-hell.flac --grid-size 16x10 --resolution 480 {{args}} 25 25 26 26 example-image out="out.png" args='': 27 - ./shapemaker image --colors colorschemes/palenight.css --resolution 3000 {{out}} {{args}} 27 + ./shapemaker image --colors colorschemes/palenight.css --resolution 1400 {{out}} {{args}}
-175
preview/engine.js
··· 1 - /** 2 - * @typedef {object} GlobalData 3 - * @property {Map<number, HTMLDivElement>} [frames] 4 - */ 5 - 6 - /** 7 - * @typedef {Window & GlobalData} WindowWithData 8 - */ 9 - 10 - function displayFrame() { 11 - const ms = millisecondsSinceStart(window.videoStartedAt) 12 - console.debug("displayFrame", ms) 13 - 14 - if (window.previouslyRenderedFrame) { 15 - let f = window.frames.get(window.previouslyRenderedFrame) 16 - if (f) { 17 - f.style.display = "none" 18 - f.classList.toggle("shown") 19 - } 20 - } 21 - 22 - let frame = closestFrame(ms) 23 - f = window.frames.get(frame) 24 - f.style.display = "block" 25 - f.classList.toggle("shown") 26 - 27 - console.debug("framestLeftCount", framesLeftCount()) 28 - if (framesLeftCount() < window.FRAMES_BUFFER_SIZE) { 29 - console.warn( 30 - window.updatingBuffer ? "already updating buffer" : "update buffer now" 31 - ) 32 - 33 - if ( 34 - !window.updatingBuffer && 35 - window.previouslyRenderedFrame - window.lastBufferUpdateWasOn > 2000 36 - ) { 37 - console.info( 38 - `Updating buffer: ${framesLeftCount()} < ${ 39 - window.FRAMES_BUFFER_SIZE 40 - } remaining and last update was ${ 41 - window.previouslyRenderedFrame - window.lastBufferUpdateWasOn 42 - } > 2000 ms ago` 43 - ) 44 - updateBuffer() 45 - } 46 - } 47 - 48 - window.previouslyRenderedFrame = frame 49 - } 50 - 51 - /** 52 - * 53 - * @returns number 54 - */ 55 - function framesLeftCount() { 56 - return [...window.frames.keys()].filter( 57 - (key) => key > window.previouslyRenderedFrame 58 - ).length 59 - } 60 - 61 - /** 62 - * 63 - * @param {number} ms 64 - * @returns {number} 65 - */ 66 - function closestFrame(ms) { 67 - const closest = [...window.frames.keys()].reduce((a, b) => 68 - Math.abs(b - ms) < Math.abs(a - ms) ? b : a 69 - ) 70 - console.debug("closestFrame", ms, "is", closest) 71 - return closest 72 - } 73 - 74 - /** 75 - * 76 - * @param {number} start 77 - * @returns 78 - */ 79 - function millisecondsSinceStart(start) { 80 - return new Date().getTime() - start 81 - } 82 - 83 - /** 84 - * 85 - * @returns {number} 86 - */ 87 - function lastLoadedFrame() { 88 - return Math.max(...[...window.frames.keys()]) 89 - } 90 - 91 - async function updateBuffer() { 92 - console.time("fetchFrames") 93 - console.log("set updatingBuffer to true") 94 - window.updatingBuffer = true 95 - console.info( 96 - "updateBuffer", 97 - 4 * window.FRAMES_BUFFER_SIZE, 98 - window.previouslyRenderedFrame 99 - ) 100 - // request half the buffer size for the next frames 101 - await fetch( 102 - new URL( 103 - "/frames?" + 104 - new URLSearchParams({ 105 - next: 4 * window.FRAMES_BUFFER_SIZE, 106 - from: window.previouslyRenderedFrame, 107 - }), 108 - window.SERVER_ORIGIN 109 - ) 110 - ).then((response) => { 111 - if (!response.ok) { 112 - console.error("Failed to fetch frames", response) 113 - return 114 - } 115 - console.timeEnd("fetchFrames") 116 - 117 - console.time("insertFramesToDOM") 118 - response.text().then((frames) => { 119 - document.body.insertAdjacentHTML("beforeend", frames) 120 - }) 121 - console.timeEnd("insertFramesToDOM") 122 - }) 123 - 124 - console.time("pruneFramesFromDOM") 125 - // remove frames that are not needed anymore 126 - ;[...window.frames.keys()].forEach((key) => { 127 - if (key < window.previouslyRenderedFrame) { 128 - window.frames.get(key).remove() 129 - } 130 - }) 131 - console.timeEnd("pruneFramesFromDOM") 132 - 133 - loadFramesFromDOM() 134 - console.log("set updatingBuffer to false") 135 - window.updatingBuffer = false 136 - window.lastBufferUpdateWasOn = window.previouslyRenderedFrame 137 - } 138 - 139 - window.addEventListener("keypress", (e) => { 140 - if (e.key === " ") { 141 - if (window.intervalID) { 142 - stopVideo() 143 - } else { 144 - startVideo() 145 - } 146 - } 147 - }) 148 - 149 - function loadFramesFromDOM() { 150 - console.time("loadFramesFromDOM") 151 - window.frames = new Map( 152 - [...document.querySelectorAll("[id^=frame-]")].map((el) => [ 153 - parseInt(el.id.replace("frame-", "")), 154 - el, 155 - ]) 156 - ) 157 - console.timeEnd("loadFramesFromDOM") 158 - } 159 - 160 - window.startVideo = () => { 161 - loadFramesFromDOM() 162 - window.refreshRate = 50 163 - window.videoStartedAt = new Date().getTime() 164 - window.previouslyRenderedFrame = null 165 - window.lastBufferUpdateWasOn = null 166 - window.updatingBuffer = false 167 - 168 - displayFrame() 169 - window.intervalID = setInterval(displayFrame, window.refreshRate) 170 - } 171 - 172 - window.stopVideo = () => { 173 - console.info("stopVideo", window.currentFrame) 174 - clearInterval(window.intervalID) 175 - }
-46
preview/index.html.hbs
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - 4 - <head> 5 - <meta charset="UTF-8"> 6 - <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 - <title>Shapemaker Preview</title> 8 - <script> 9 - window.SERVER_ORIGIN = "{{ serverorigin }}"; 10 - window.FRAMES_BUFFER_SIZE = {{ framesbuffersize }}; 11 - {{{ enginesource }}} 12 - </script> 13 - {{!-- <script src="preview/engine.js"></script> --}} 14 - <style> 15 - .frame.shown { 16 - position: fixed; 17 - top: 0; 18 - left: 0; 19 - right: 0; 20 - bottom: 0; 21 - } 22 - 23 - .frame.shown>svg { 24 - width: 100%; 25 - height: 100%; 26 - } 27 - 28 - audio { 29 - position: fixed; 30 - z-index: 100; 31 - bottom: 0; 32 - left: 0; 33 - } 34 - </style> 35 - </head> 36 - 37 - <body style="background-color: {{background}};"> 38 - <audio src="{{ audiopath }}" controls onplay="startVideo()" onpause="stopVideo()"></audio> 39 - {{#each frames}} 40 - <div style="display: none;" id="frame-{{@key}}" class="frame"> 41 - {{{ this }}} 42 - </div> 43 - {{/each}} 44 - </body> 45 - 46 - </html>
+127 -38
src/canvas.rs
··· 1 1 use core::panic; 2 - use std::{collections::HashMap, io::Write as _, ops::Range}; 2 + use std::{collections::HashMap, ops::Range}; 3 3 4 - use anyhow::Result; 5 4 use itertools::Itertools as _; 5 + use measure_time::info_time; 6 6 use rand::Rng; 7 7 8 8 use crate::{ ··· 24 24 pub background: Option<Color>, 25 25 26 26 pub world_region: Region, 27 + 28 + /// Render cache for the SVG string. Prevents having to re-calculate a pixmap when the SVG hasn't changed. 29 + png_render_cache: Option<String>, 27 30 } 28 31 29 32 impl Canvas { ··· 187 190 layers: vec![], 188 191 world_region: Region::new(0, 0, 3, 3).unwrap(), 189 192 background: None, 193 + png_render_cache: None, 190 194 } 191 195 } 192 196 ··· 414 418 self.remove_background() 415 419 } 416 420 417 - pub fn save_as( 421 + // previous_frame_at gives path to the previously rendered frame, which allows to copy on cache hits instead of having to re-write bytes again 422 + pub fn render_to_png( 423 + &mut self, 418 424 at: &str, 419 - aspect_ratio: f32, 420 - resolution: usize, 421 - rendered: String, 422 - ) -> Result<(), String> { 425 + resolution: u32, 426 + previous_frame_at: Option<&str>, 427 + ) -> anyhow::Result<()> { 428 + info_time!("render_to_png"); 429 + let aspect_ratio = self.aspect_ratio(); 423 430 let (height, width) = if aspect_ratio > 1.0 { 424 431 // landscape: resolution is width 425 - (resolution, (resolution as f32 * aspect_ratio) as usize) 432 + (resolution, (resolution as f32 * aspect_ratio) as u32) 426 433 } else { 427 434 // portrait: resolution is height 428 - ((resolution as f32 / aspect_ratio) as usize, resolution) 435 + ((resolution as f32 / aspect_ratio) as u32, resolution) 429 436 }; 430 437 431 - let mut spawned = std::process::Command::new("resvg") 432 - .args(["--background", "transparent"]) 433 - .args(["--width", &format!("{width}")]) 434 - .args(["--height", &format!("{height}")]) 435 - .args(["--use-font-file", "Inconsolata-Bold.ttf"]) 436 - .args(["--resources-dir", "."]) 437 - .arg("-") 438 - .arg(at) 439 - .stdin(std::process::Stdio::piped()) 440 - .spawn() 441 - .unwrap(); 438 + if let Some(previous_frame_at) = previous_frame_at { 439 + match self.render_to_pixmap(width, height)? { 440 + None => { 441 + std::fs::copy(previous_frame_at, at)?; 442 + } 443 + Some(pixmap) => { 444 + pixmap_to_png_data(pixmap).and_then(|data| write_png_data(data, at))? 445 + } 446 + } 447 + return Ok(()); 448 + } 449 + 450 + Ok(self 451 + .render_to_pixmap_no_cache(width, height) 452 + .and_then(|pixmap| { 453 + pixmap_to_png_data(pixmap).and_then(|data| write_png_data(data, at)) 454 + })?) 455 + } 456 + } 442 457 443 - let stdin = spawned.stdin.as_mut().unwrap(); 444 - stdin.write_all(rendered.as_bytes()).unwrap(); 458 + fn pixmap_to_png_data(pixmap: tiny_skia::Pixmap) -> anyhow::Result<Vec<u8>> { 459 + info_time!("\tpixmap_to_png_data"); 460 + Ok(pixmap.encode_png()?) 461 + } 445 462 446 - match spawned.wait_with_output() { 447 - Ok(_) => Ok(()), 448 - Err(e) => Err(format!("Failed to execute convert: {}", e)), 449 - } 450 - } 463 + fn write_png_data(data: Vec<u8>, at: &str) -> anyhow::Result<()> { 464 + info_time!("\twrite_png_data"); 465 + std::fs::write(at, data)?; 466 + Ok(()) 451 467 } 452 468 453 469 impl Canvas { ··· 514 530 ) 515 531 } 516 532 517 - pub fn render(&mut self, render_background: bool) -> Result<String> { 533 + pub fn render_to_svg(&mut self) -> anyhow::Result<String> { 534 + info_time!("render_to_svg"); 518 535 let background_color = self.background.unwrap_or_default(); 519 536 let mut svg = svg::Document::new(); 520 - if render_background { 521 - svg = svg.add( 522 - svg::node::element::Rectangle::new() 523 - .set("x", -(self.canvas_outter_padding as i32)) 524 - .set("y", -(self.canvas_outter_padding as i32)) 525 - .set("width", self.width()) 526 - .set("height", self.height()) 527 - .set("fill", background_color.render(&self.colormap)), 528 - ); 529 - } 537 + svg = svg.add( 538 + svg::node::element::Rectangle::new() 539 + .set("x", -(self.canvas_outter_padding as i32)) 540 + .set("y", -(self.canvas_outter_padding as i32)) 541 + .set("width", self.width()) 542 + .set("height", self.height()) 543 + .set("fill", background_color.render(&self.colormap)), 544 + ); 545 + 530 546 for layer in self.layers.iter_mut().filter(|layer| !layer.hidden).rev() { 531 547 svg = svg.add(layer.render(self.colormap.clone(), self.cell_size, layer.object_sizes)); 532 548 } ··· 559 575 560 576 Ok(rendered) 561 577 } 578 + 579 + pub fn render_to_pixmap_no_cache( 580 + &mut self, 581 + width: u32, 582 + height: u32, 583 + ) -> anyhow::Result<tiny_skia::Pixmap> { 584 + info_time!("render_to_pixmap_no_cache"); 585 + let mut pixmap = self.create_pixmap(width, height); 586 + 587 + let parsed_svg = &svg_to_usvg_tree(&self.render_to_svg()?)?; 588 + 589 + self.usvg_tree_to_pixmap(width, height, pixmap.as_mut(), parsed_svg); 590 + 591 + Ok(pixmap) 592 + } 593 + 594 + // Returns None if we had a render cache hit -- pixmap is in self.png_render_cache in that case 595 + pub fn render_to_pixmap( 596 + &mut self, 597 + width: u32, 598 + height: u32, 599 + ) -> anyhow::Result<Option<tiny_skia::Pixmap>> { 600 + info_time!("render_to_pixmap"); 601 + 602 + let new_svg_contents = self.render_to_svg()?; 603 + if let Some(cached_svg) = &self.png_render_cache { 604 + if *cached_svg == new_svg_contents { 605 + // TODO find a way to avoid .cloneing the pixmap 606 + return Ok(None); 607 + } 608 + } 609 + 610 + let mut pixmap = self.create_pixmap(width, height); 611 + 612 + let parsed_svg = &svg_to_usvg_tree(&new_svg_contents)?; 613 + 614 + self.usvg_tree_to_pixmap(width, height, pixmap.as_mut(), parsed_svg); 615 + 616 + self.png_render_cache = Some(new_svg_contents); 617 + 618 + Ok(Some(pixmap)) 619 + } 620 + 621 + fn usvg_tree_to_pixmap( 622 + &mut self, 623 + width: u32, 624 + height: u32, 625 + mut pixmap_mut: tiny_skia::PixmapMut<'_>, 626 + parsed_svg: &resvg::usvg::Tree, 627 + ) { 628 + info_time!("usvg_tree_to_pixmap"); 629 + resvg::render( 630 + parsed_svg, 631 + tiny_skia::Transform::from_scale( 632 + width as f32 / self.width() as f32, 633 + height as f32 / self.height() as f32, 634 + ), 635 + &mut pixmap_mut, 636 + ); 637 + } 638 + 639 + fn create_pixmap(&self, width: u32, height: u32) -> tiny_skia::Pixmap { 640 + info_time!("create_pixmap"); 641 + tiny_skia::Pixmap::new(width, height).expect("Failed to create pixmap") 642 + } 643 + } 644 + 645 + fn svg_to_usvg_tree(svg: &str) -> anyhow::Result<resvg::usvg::Tree> { 646 + info_time!("svg_to_usvg_tree"); 647 + Ok(resvg::usvg::Tree::from_str( 648 + svg, 649 + &resvg::usvg::Options::default(), 650 + )?) 562 651 }
+3 -3
src/cli.rs
··· 1 1 use docopt::Docopt; 2 + use measure_time::info_time; 2 3 use serde::Deserialize; 3 4 use crate::{Canvas, ColorMapping}; 4 5 ··· 38 39 --audio <file> Audio file to use for the video 39 40 --duration <seconds> Number of seconds to render. If not set, the video will be as long as the audio file. 40 41 --start <seconds> Start the video at this time in seconds. [default: 0] 41 - --preview Only create preview.html, not the output video. Preview.html will be created in the same directory as <file>, but <file> will not be created. 42 42 --sync-with <directory> Directory containing the audio files to sync to. 43 43 The directory must contain: 44 44 - stems/(instrument name).wav — stems ··· 63 63 } 64 64 65 65 pub fn canvas_from_cli(args: &Args) -> Canvas { 66 + info_time!("canvas_from_cli"); 66 67 let mut canvas = Canvas::new(vec![]); 67 68 canvas.colormap = load_colormap(args); 68 69 set_canvas_settings_from_args(args, &mut canvas); ··· 90 91 pub flag_fps: Option<usize>, 91 92 pub flag_sync_with: Option<String>, 92 93 pub flag_audio: Option<String>, 93 - pub flag_resolution: Option<usize>, 94 + pub flag_resolution: Option<u32>, 94 95 pub flag_workers: Option<usize>, 95 96 pub flag_duration: Option<usize>, 96 97 pub flag_start: Option<usize>, 97 - pub flag_preview: bool, 98 98 } 99 99 100 100 fn set_canvas_settings_from_args(args: &Args, canvas: &mut Canvas) {
-1
src/lib.rs
··· 12 12 pub mod midi; 13 13 pub mod objects; 14 14 pub mod point; 15 - pub mod preview; 16 15 pub mod region; 17 16 pub mod sync; 18 17 pub mod transform;
+27 -18
src/main.rs
··· 1 - use std::env; 2 - 3 1 use anyhow::Result; 4 - use itertools::Itertools; 5 - use rand::Rng; 2 + use measure_time::info_time; 6 3 use shapemaker::{ 7 4 cli::{canvas_from_cli, cli_args}, 8 5 *, 9 6 }; 10 7 8 + #[macro_use] 9 + extern crate log; 10 + 11 11 pub fn main() -> Result<()> { 12 + env_logger::init(); 12 13 run(cli_args()) 13 14 } 14 15 15 16 pub fn run(args: cli::Args) -> Result<()> { 17 + info_time!("run"); 16 18 let mut canvas = canvas_from_cli(&args); 17 19 18 20 if args.cmd_image && !args.cmd_video { 19 21 canvas = examples::title(); 20 22 21 - let rendered = canvas.render(true)?; 22 23 if args.arg_file.ends_with(".svg") { 23 - std::fs::write(args.arg_file, rendered).unwrap(); 24 + std::fs::write(args.arg_file, canvas.render_to_svg()?).unwrap(); 24 25 } else { 25 - match Canvas::save_as( 26 - &args.arg_file, 27 - canvas.aspect_ratio(), 28 - args.flag_resolution.unwrap_or(1000), 29 - rendered, 30 - ) { 26 + match canvas.render_to_png(&args.arg_file, args.flag_resolution.unwrap_or(1000), None) { 31 27 Ok(_) => println!("Image saved to {}", args.arg_file), 32 28 Err(e) => println!("Error saving image: {}", e), 33 29 } ··· 39 35 video.duration_override = args.flag_duration.map(|seconds| seconds * 1000); 40 36 video.start_rendering_at = args.flag_start.unwrap_or_default() * 1000; 41 37 video.fps = args.flag_fps.unwrap_or(30); 42 - 43 - if args.flag_preview { 44 - video.preview_on(8888) 45 - } else { 46 - video.render_to(args.arg_file, args.flag_workers.unwrap_or(8), false) 47 - } 38 + video.audiofile = args 39 + .flag_audio 40 + .expect("Provide audio with --audio to render a video") 41 + .into(); 42 + video 43 + .sync_audio_with( 44 + &args 45 + .flag_sync_with 46 + .expect("Provide MIDI sync file with --sync-with to render a video"), 47 + ) 48 + .each_beat(&|canvas, ctx| { 49 + canvas.background = Some(if ctx.beat % 2 == 0 { 50 + Color::Black 51 + } else { 52 + Color::White 53 + }); 54 + Ok(()) 55 + }) 56 + .render(args.arg_file) 48 57 }
-112
src/preview.rs
··· 1 - use std::{collections::HashMap, fs, path::PathBuf}; 2 - 3 - use anyhow::Result; 4 - use handlebars::Handlebars; 5 - use itertools::Itertools; 6 - use serde_json::json; 7 - 8 - use crate::Canvas; 9 - 10 - const FRAMES_BUFFER_SIZE: usize = 500; 11 - 12 - pub fn render_template( 13 - frames: &HashMap<usize, String>, 14 - canvas: &Canvas, 15 - path_to_audio_file: PathBuf, 16 - port: usize, 17 - ) -> String { 18 - let template = String::from_utf8_lossy(include_bytes!("../preview/index.html.hbs")); 19 - let engine_js_source = String::from_utf8_lossy(include_bytes!("../preview/engine.js")); 20 - 21 - let hbs = Handlebars::new(); 22 - hbs.render_template( 23 - &template, 24 - &json!({ 25 - "frames":frames, 26 - "audiopath": path_to_audio_file, 27 - "enginesource": engine_js_source, 28 - "background": canvas.background.map_or("black".to_string(), |color| color.render(&canvas.colormap)), 29 - "serverorigin": format!("http://localhost:{}", port), 30 - "framesbuffersize": FRAMES_BUFFER_SIZE, 31 - }), 32 - ) 33 - .unwrap() 34 - } 35 - 36 - // rendered_svg_frames should map ms timestamps to SVG strings 37 - pub fn output_preview( 38 - canvas: &Canvas, 39 - rendered_svg_frames: &HashMap<usize, String>, 40 - server_port: usize, 41 - output_file: PathBuf, 42 - audio_file: PathBuf, 43 - ) -> Result<()> { 44 - let first_frames = rendered_svg_frames 45 - .iter() 46 - // over 3000 loaded frames get really heavy on the browser (too much DOM nodes) 47 - .sorted_by_key(|(ms, _)| *ms) 48 - .take((2 * FRAMES_BUFFER_SIZE).min(10_000)) 49 - .map(|(ms, svg)| (*ms, svg.clone())) 50 - .collect::<HashMap<usize, String>>(); 51 - 52 - let contents = render_template(&first_frames, canvas, audio_file, server_port); 53 - fs::write(output_file, contents)?; 54 - Ok(()) 55 - } 56 - 57 - pub fn start_preview_server(port: usize, frames: HashMap<usize, String>) -> Result<()> { 58 - let server = tiny_http::Server::http(format!("0.0.0.0:{}", port)).unwrap(); 59 - println!("Preview server running on port {}", port); 60 - let sorted_frames: Vec<(&usize, &String)> = 61 - frames.iter().sorted_by_key(|(ms, _)| *ms).collect(); 62 - println!("{} frames available", sorted_frames.len()); 63 - 64 - for request in server.incoming_requests() { 65 - let (frame_start_ms, requested_frames_count) = get_request_params(request.url()); 66 - 67 - println!( 68 - "Request for {} frames @ {}ms", 69 - requested_frames_count, frame_start_ms, 70 - ); 71 - 72 - let contents = sorted_frames 73 - .iter() 74 - .filter(|(ms, _)| **ms >= frame_start_ms) 75 - .take(requested_frames_count) 76 - .map(|(ms, svg_string)| { 77 - format!( 78 - r#"<div style="display: none;" id="frame-{}" class="frame">{}</div>"#, 79 - ms, svg_string 80 - ) 81 - }) 82 - .join("\n"); 83 - 84 - request.respond(tiny_http::Response::from_string(contents).with_header( 85 - tiny_http::Header { 86 - field: "Access-Control-Allow-Origin".parse().unwrap(), 87 - value: "*".parse().unwrap(), 88 - }, 89 - ))?; 90 - } 91 - Ok(()) 92 - } 93 - 94 - // returns (ms timestamp of first frame to send, number of frames to send) 95 - fn get_request_params(url: &str) -> (usize, usize) { 96 - let mut first_frame_ms = 0; 97 - let mut num_frames = 1; 98 - 99 - let (_, querystring) = url.split_once('?').unwrap_or(("", "")); 100 - for (key, value) in querystring 101 - .split('&') 102 - .map(|pair| pair.split_once('=').unwrap_or(("", ""))) 103 - { 104 - match key { 105 - "from" => first_frame_ms = value.parse().unwrap_or(0), 106 - "next" => num_frames = value.parse().unwrap_or(1), 107 - _ => (), 108 - } 109 - } 110 - 111 - (first_frame_ms, num_frames) 112 - }
+30 -152
src/video.rs
··· 1 1 use std::process; 2 2 use std::{ 3 - cmp::min, 4 - collections::HashMap, 5 3 fmt::Formatter, 6 4 fs::{create_dir, create_dir_all, remove_dir_all}, 7 5 panic, 8 6 path::{Path, PathBuf}, 9 - sync::Arc, 10 7 }; 11 8 12 - use std::thread; 13 - 14 9 use anyhow::Result; 15 10 use chrono::{DateTime, NaiveDateTime}; 16 11 use indicatif::{ProgressBar, ProgressIterator}; 12 + use measure_time::info_time; 17 13 18 14 use crate::{ 19 - preview, 20 15 sync::SyncData, 21 16 ui::{self, format_log_msg, setup_progress_bar, Log as _}, 22 17 Canvas, ColoredObject, Context, LayerAnimationUpdateFunction, MidiSynchronizer, ··· 49 44 pub frames_output_directory: &'static str, 50 45 pub syncdata: SyncData, 51 46 pub audiofile: PathBuf, 52 - pub resolution: usize, 47 + pub resolution: u32, 53 48 pub duration_override: Option<usize>, 54 49 pub start_rendering_at: usize, 55 50 pub progress_bar: indicatif::ProgressBar, 56 51 } 52 + 57 53 pub struct Hook<C> { 58 54 pub when: Box<HookCondition<C>>, 59 55 pub render_function: Box<RenderFunction<C>>, ··· 122 118 } 123 119 124 120 pub fn sync_audio_with(self, sync_data_path: &str) -> Self { 121 + info_time!("sync_audio_with"); 125 122 if sync_data_path.ends_with(".mid") || sync_data_path.ends_with(".midi") { 126 123 let loader = MidiSynchronizer::new(sync_data_path); 127 124 let syncdata = loader.load(Some(&self.progress_bar)); ··· 192 189 } 193 190 } 194 191 195 - fn build_frame( 196 - svg_string: String, 197 - frame_no: usize, 198 - total_frames: usize, 199 - frames_output_directory: &str, 200 - aspect_ratio: f32, 201 - resolution: usize, 202 - ) -> Result<(), String> { 203 - Canvas::save_as( 204 - &format!( 205 - "{}/{:0width$}.png", 206 - frames_output_directory, 207 - frame_no, 208 - width = total_frames.to_string().len() 209 - ), 210 - aspect_ratio, 211 - resolution, 212 - svg_string, 192 + fn frame_output_path(&self, frame_no: usize) -> String { 193 + format!( 194 + "{}/{:0width$}.png", 195 + self.frames_output_directory, 196 + frame_no, 197 + width = self.total_frames().to_string().len() 213 198 ) 214 199 } 215 200 ··· 515 500 .expect("No audio sync data provided. Use .sync_audio_with() to load a MIDI file, or provide a duration override.") 516 501 } 517 502 518 - pub fn preview_on(&self, port: usize) -> Result<()> { 519 - let mut rendered_frames: HashMap<usize, String> = HashMap::new(); 520 - let progress_bar = self.setup_progress_bar(); 521 - 522 - for (frame, _, ms) in self.render_frames(&progress_bar, true)? { 523 - rendered_frames.insert(ms, frame); 524 - } 525 - 526 - progress_bar.finish_and_clear(); 527 - 528 - preview::output_preview( 529 - &self.initial_canvas, 530 - &rendered_frames, 531 - port, 532 - PathBuf::from(".").join("preview.html"), 533 - self.audiofile.clone(), 534 - )?; 535 - 536 - preview::start_preview_server(port, rendered_frames) 537 - } 538 - 539 - pub fn render_to( 540 - &self, 541 - output_file: String, 542 - workers_count: usize, 543 - preview_only: bool, 544 - ) -> Result<()> { 545 - self.render(output_file, true, workers_count, preview_only) 546 - } 547 - 548 - pub fn render_layers_in(&self, output_directory: String, workers_count: usize) -> Result<()> { 503 + pub fn render_layers_in(&self, output_directory: String) -> Result<()> { 549 504 for composition in self 550 505 .initial_canvas 551 506 .layers 552 507 .iter() 553 508 .map(|l| vec![l.name.as_str()]) 554 509 { 555 - self.render( 556 - format!("{}/{}.mov", output_directory, composition.join("+")), 557 - false, 558 - workers_count, 559 - false, 560 - )?; 510 + self.render(format!( 511 + "{}/{}.mov", 512 + output_directory, 513 + composition.join("+") 514 + ))?; 561 515 } 562 516 Ok(()) 563 517 } 564 518 565 - // Returns a triple of (SVG content, frame number, millisecond at frame) 566 - pub fn render_frames( 567 - &self, 568 - progress_bar: &ProgressBar, 569 - render_background: bool, 570 - ) -> Result<Vec<(String, usize, usize)>> { 519 + // Saves PNG frames to disk. Returns number of frames written. 520 + pub fn render_frames(&self, progress_bar: &ProgressBar) -> Result<usize> { 521 + let mut written_frames_count: usize = 0; 571 522 let mut context = Context { 572 523 frame: 0, 573 524 beat: 0, ··· 586 537 587 538 let mut previous_rendered_beat = 0; 588 539 let mut previous_rendered_frame = 0; 589 - let mut frames_to_write: Vec<(String, usize, usize)> = vec![]; 590 540 591 541 let render_ms_range = 0..self.duration_ms() + self.start_rendering_at; 592 542 ··· 661 611 } 662 612 663 613 if context.frame != previous_rendered_frame { 664 - let rendered = canvas.render(render_background)?; 614 + canvas.render_to_png( 615 + &self.frame_output_path(context.frame), 616 + self.resolution, 617 + Some(&self.frame_output_path(previous_rendered_frame)), 618 + )?; 619 + written_frames_count += 1; 665 620 666 621 previous_rendered_beat = context.beat; 667 622 previous_rendered_frame = context.frame; 668 - 669 - frames_to_write.push((rendered, context.frame, context.ms)) 670 623 } 671 624 } 672 625 673 - Ok(frames_to_write) 626 + Ok(written_frames_count) 674 627 } 675 628 676 629 pub fn setup_progress_bar(&self) -> ProgressBar { 677 630 ui::setup_progress_bar(self.total_frames() as u64, "Rendering") 678 631 } 679 632 680 - pub fn render( 681 - &self, 682 - output_file: String, 683 - render_background: bool, 684 - workers_count: usize, 685 - _preview_only: bool, 686 - ) -> Result<()> { 687 - // Ensure resvg is installed 688 - if !is_binary_installed("resvg") { 689 - panic!("resvg is not installed. Please install it by running `cargo install resvg`."); 690 - } 633 + pub fn render(&self, output_file: String) -> Result<()> { 634 + info_time!("render"); 691 635 // Ensure ffmpeg is installed 692 636 if !is_binary_installed("ffmpeg") { 693 637 panic!("ffmpeg is not installed. Please install it."); 694 638 } 695 639 696 - let mut frame_writer_threads = vec![]; 697 - let mut frames_to_write: Vec<(String, usize, usize)> = vec![]; 698 - 699 640 create_dir_all(self.frames_output_directory)?; 700 641 remove_dir_all(self.frames_output_directory)?; 701 642 create_dir(self.frames_output_directory)?; 702 643 create_dir_all(Path::new(&output_file).parent().unwrap())?; 703 - 704 - let total_frames = self.total_frames(); 705 - let aspect_ratio = 706 - self.initial_canvas.grid_size.0 as f32 / self.initial_canvas.grid_size.1 as f32; 707 - let resolution = self.resolution; 708 644 709 645 self.progress_bar.set_position(0); 710 646 self.progress_bar.set_prefix("Rendering"); 711 647 self.progress_bar.set_message(""); 712 648 713 - for (frame, no, ms) in self.render_frames(&self.progress_bar, render_background)? { 714 - frames_to_write.push((frame, no, ms)); 715 - } 649 + let frames_written = self.render_frames(&self.progress_bar)?; 716 650 717 - self.progress_bar.log( 718 - "Rendered", 719 - &format!("{} frames to SVG", frames_to_write.len()), 720 - ); 721 - 722 - frames_to_write.retain(|(_, _, ms)| *ms >= self.start_rendering_at); 723 - 724 - self.progress_bar.set_prefix("Converting"); 725 651 self.progress_bar 726 - .set_message("converting SVG frames to PNG"); 727 - self.progress_bar.set_position(0); 728 - self.progress_bar.set_length(frames_to_write.len() as u64); 729 - 730 - for (frame, no, _) in &frames_to_write { 731 - std::fs::write( 732 - format!("{}/{}.svg", self.frames_output_directory, no), 733 - frame, 734 - )?; 735 - } 736 - 737 - let chunk_size = (frames_to_write.len() as f32 / workers_count as f32).ceil() as usize; 738 - let frames_to_write = Arc::new(frames_to_write); 739 - let frames_output_directory = self.frames_output_directory; 740 - for i in 0..workers_count { 741 - let frames_to_write = Arc::clone(&frames_to_write); 742 - let progress_bar = self.progress_bar.clone(); 743 - frame_writer_threads.push( 744 - thread::Builder::new() 745 - .name(format!("worker-{}", i)) 746 - .spawn(move || { 747 - for (frame_svg, frame_no, _) in &frames_to_write 748 - [i * chunk_size..min((i + 1) * chunk_size, frames_to_write.len())] 749 - { 750 - Video::<AdditionalContext>::build_frame( 751 - frame_svg.clone(), 752 - *frame_no, 753 - total_frames, 754 - frames_output_directory, 755 - aspect_ratio, 756 - resolution, 757 - ) 758 - .unwrap(); 759 - progress_bar.inc(1); 760 - } 761 - }) 762 - .unwrap(), 763 - ); 764 - } 765 - 766 - for handle in frame_writer_threads { 767 - handle.join().unwrap(); 768 - } 769 - 770 - self.progress_bar.log( 771 - "Converted", 772 - &format!("{} SVG frames to PNG", self.progress_bar.position()), 773 - ); 774 - self.progress_bar.finish_and_clear(); 652 + .log("Rendered", &format!("{} frames to SVG", frames_written)); 775 653 776 654 let spinner = ui::Spinner::start("Building", "video"); 777 655 let result = self.build_video(&output_file);
+8 -31
src/web.rs
··· 14 14 WEB_CANVAS.lock().unwrap() 15 15 } 16 16 17 - 18 17 // Can't bind Color.name directly, see https://github.com/rustwasm/wasm-bindgen/issues/1715 19 18 #[wasm_bindgen] 20 19 pub fn color_name(c: Color) -> String { ··· 60 59 61 60 #[wasm_bindgen] 62 61 pub fn render_canvas_into(selector: String) { 63 - let svgstring = canvas().render(false).unwrap_throw(); 62 + let svgstring = canvas().render_to_svg().unwrap_throw(); 64 63 append_new_div_inside(svgstring, selector) 65 64 } 66 65 67 66 #[wasm_bindgen] 68 67 pub fn render_canvas_at(selector: String) { 69 - let svgstring = canvas().render(false).unwrap_throw(); 68 + let svgstring = canvas().render_to_svg().unwrap_throw(); 70 69 replace_content_with(svgstring, selector) 71 70 } 72 71 ··· 130 129 } 131 130 132 131 #[wasm_bindgen] 133 - pub fn render_canvas(render_background: Option<bool>) { 134 - canvas() 135 - .render(render_background.unwrap_or(false)) 136 - .unwrap_throw(); 132 + pub fn render_canvas() { 133 + canvas().render_to_svg().unwrap_throw(); 137 134 } 138 135 139 136 #[wasm_bindgen] ··· 203 200 #[wasm_bindgen] 204 201 impl LayerWeb { 205 202 pub fn render(&self) -> String { 206 - canvas().render(false).unwrap_throw() 203 + canvas().render_to_svg().unwrap_throw() 207 204 } 208 205 209 206 pub fn render_into(&self, selector: String) { ··· 229 226 } 230 227 } 231 228 232 - pub fn new_line( 233 - &self, 234 - name: &str, 235 - start: Point, 236 - end: Point, 237 - thickness: f32, 238 - color: Color, 239 - ) { 229 + pub fn new_line(&self, name: &str, start: Point, end: Point, thickness: f32, color: Color) { 240 230 canvas().layer(name).add_object( 241 231 name, 242 232 ( ··· 287 277 .layer(name) 288 278 .add_object(name, Object::BigCircle(center).color(Fill::Solid(color))) 289 279 } 290 - pub fn new_text( 291 - &self, 292 - name: &str, 293 - anchor: Point, 294 - text: String, 295 - font_size: f32, 296 - color: Color, 297 - ) { 280 + pub fn new_text(&self, name: &str, anchor: Point, text: String, font_size: f32, color: Color) { 298 281 canvas().layer(name).add_object( 299 282 name, 300 283 Object::Text(anchor, text, font_size).color(Fill::Solid(color)), 301 284 ) 302 285 } 303 - pub fn new_rectangle( 304 - &self, 305 - name: &str, 306 - topleft: Point, 307 - bottomright: Point, 308 - color: Color, 309 - ) { 286 + pub fn new_rectangle(&self, name: &str, topleft: Point, bottomright: Point, color: Color) { 310 287 canvas().layer(name).add_object( 311 288 name, 312 289 Object::Rectangle(topleft, bottomright).color(Fill::Solid(color)),