├── ARCHITECTURE.md ├── ActivityDiagram.dia ├── ActivityDiagram.svg ├── CHANGELOG ├── LICENSE ├── README.md ├── StateMachineDiagram.dia ├── StateMachineDiagram.svg ├── recreate_userconf.sh ├── script-opts └── metadata_osd.conf ├── scripts └── metadata_osd.lua └── sshots ├── sshot_albumtracknumber.png ├── sshot_chapternumber.png ├── sshot_osd_1_dark.png └── sshot_osd_1_light.png /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Metadata OSD script for mpv player 2 | 3 | ## Program Design 4 | 5 | ### Metadata Selection 6 | 7 | - If the currently playing file is not a video file and has internal **chapters**, these are preferred and shown on the OSD, otherwise file metadata is selected and shown. 8 | 9 | - _Rationale_: Implemented to support music files accompanied with a .cue file where each album track is technically a chapter. 10 | 11 | - If autohide is enabled and chapter metadata is available, a second OSD (OSD-2) will show up after OSD-1 had been auto-hidden. In the event of a chapter change during playback, only the second OSD (OSD-2) with chapter title will show up. 12 | 13 | - _Rationale_: Chapter metadata for video files could have been integrated into OSD-1, but displaying the whole dataset repeatedly is disturbing. 14 | 15 | - Directory / file name fallback for files with no metadata. 16 | 17 | - _Rationale_: File name and directory name often carry similar information as the per file-format specific metadata. This approach works for media collections organized as e.g.: 18 | - `//` or 19 | - `//` 20 | - This can be a topic of further changes. 21 | 22 | ## Program Architecture 23 | 24 | ### Partial Functional UML Diagrams 25 | 26 | ![State Machine Diagram](StateMachineDiagram.svg) 27 | 28 | ![Activity Diagram](ActivityDiagram.svg) 29 | 30 | * UML diagrams created with Dia. [http://live.gnome.org/Dia](http://live.gnome.org/Dia) 31 | -------------------------------------------------------------------------------- /ActivityDiagram.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vc-01/metadata-osd/c8581ea1786c8fce1cfec570c17dd07f8fafc9ea/ActivityDiagram.dia -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | metadata-osd-0.6.2 / 2023-12-25 2 | =============================== 3 | * fix: Occassional crashes on file path evaluation 4 | 5 | metadata-osd-0.6.1 / 2023-11-01 6 | =============================== 7 | * fix: Chapter title (offset) for audio files with 8 | internal chapters 9 | 10 | metadata-osd-0.6.0 / 2023-07-02 11 | =============================== 12 | * Added: Pathname fallback on/off switch options: 13 | enable_pathname_fallback_* 14 | 15 | metadata-osd-0.5.4 / 2023-03-23 16 | =============================== 17 | * Don't use 'goto' statement to support Lua 5.1 18 | 19 | metadata-osd-0.5.3 / 2023-03-09 20 | =============================== 21 | * fix: Parse paths with backslashes for pathname fallback 22 | 23 | metadata-osd-0.5.2 / 2023-03-07 24 | =============================== 25 | * fix: Reset OSD data before loading new file 26 | 27 | metadata-osd-0.5.1 / 2023-02-26 28 | =============================== 29 | * fix: Apply textarea_2 styling options also to textarea_2_reldate 30 | * fix: Remove extraneous quot marks from example configuration file 31 | 32 | metadata-osd-0.5.0 / 2023-02-10 33 | =============================== 34 | * BREAKING CHANGES: 35 | - user config files named 'metadata-osd.conf' must be 36 | renamed to 'metadata_osd.conf' (version 0.1.0 heritage) 37 | - config option 'show_current_chapter_number' must be 38 | renamed to 'show_chapternumber' 39 | - config option 'show_current_albumtrack_number' or 40 | 'show_albumtrack_number' must be renamed to 41 | 'show_albumtracknumber' 42 | * New user config options 43 | - show_chapternumber 44 | - show_albumtracknumber 45 | * Exposed key binding names for input.conf 46 | * Disabled by default while viewing pictures 47 | * Matched autohide delays of 'metadata-osd' and 'status osd' 48 | * Removing newline characters in OSD messages 49 | * Not showing album's track number by default 50 | * New styling config options 51 | * OSD message truncate on UTF-8 characters (not bytes) 52 | 53 | metadata-osd-0.4.1 / 2022-10-02 54 | =============================== 55 | * fix: Add missing check in year metadata pattern extraction 56 | 57 | metadata-osd-0.4.0 / 2022-09-24 58 | =============================== 59 | * Tune metadata processing: 60 | - Cut out and show just the year (not the full date, if present) 61 | - Support more possible keys for metadata, namely: 62 | - 'album_artist' 63 | - 'composer' 64 | - Show album's track if not equal to playlist's track 65 | (playlists can be long and traversing multiple directories) 66 | 67 | metadata-osd-0.3.0 / 2022-08-31 68 | =============================== 69 | * Added: Config option to disable OSD-2 70 | 71 | metadata-osd-0.2.0 / 2022-08-20 72 | =============================== 73 | * Added: Media-type related config options: 74 | enable_for_audio 75 | enable_for_audio_withalbumart 76 | enable_for_video 77 | enable_for_image 78 | autohide_for_audio 79 | autohide_for_audio_withalbumart 80 | autohide_for_video 81 | autohide_for_image 82 | 83 | * Removed: Autohide auto-decide config option: 84 | key_toggleautohide_autodecide 85 | 86 | * Changed: Minus character to underscore change 87 | in both script name and config file name. 88 | Existing configuration file with minus character 89 | will still work (for now). 90 | 91 | metadata-osd-0.1.0 / 2022-06-18 92 | =============================== 93 | * Initial release 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022-2023 Vladimir Chren 4 | All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Metadata OSD script for mpv player 2 | 3 | This script adds metadata OSD (on-screen display) to mpv. 4 | 5 | 6 | 7 | 8 | OSD-1 Screenshot 9 | 10 | 11 | * Screenshot taken on a packaged mpv on Arch Linux; font type and size may vary on other platforms. 12 | 13 | ## Installation & Configuration 14 | 15 | Download & place [metadata_osd.lua](scripts/metadata_osd.lua?raw=true) script into 16 | 17 | - `$XDG_CONFIG_HOME/mpv/scripts` (it will be most of the times `~/.config/mpv/scripts`) on Linux, or 18 | - `%APPDATA%\mpv\scripts` on Windows 19 | 20 | for autoload. 21 | 22 | (_Optional_) Config file with user settings named `metadata_osd.conf` can be created in 23 | 24 | - `$XDG_CONFIG_HOME/mpv/script-opts` on Linux, or 25 | - `%APP‐DATA%\mpv\script-opts` on Windows 26 | 27 | See the example configuration file [metadata_osd.conf](script-opts/metadata_osd.conf?raw=true) in this repo for available user config options. 28 | 29 | ## Key Bindings 30 | 31 | The following table summarizes the script's default key bindings and their config options: 32 | 33 | | Key | Action | Config Option Name | Binding Name (for input.conf) | 34 | | -------------:|:------------------------------------- |:--------------------- |:----------------------------- | 35 | | F1 | Master enable / disable (killswitch) | key_toggleenable | toggleenable | 36 | | F5 | Enable / disable the autohide feature | key_toggleautohide | toggleautohide | 37 | | _unassigned_ | Show / hide OSD-1 | key_toggleosd_1 | toggleosd_1 | 38 | | _unassigned_ | Show / hide OSD-2 | key_toggleosd_2 | toggleosd_2 | 39 | | F6 | Reset any user-toggled switches | key_reset_usertoggled | reset_usertoggled | 40 | | _unassigned_ | Show status OSD | key_showstatusosd | showstatusosd | 41 | 42 | Key bindings can be configured either via script's config file, see [metadata_osd.conf](script-opts/metadata_osd.conf?raw=true) example with pre-filled defaults, or via _input.conf_. 43 | 44 | Default bindings in _input.conf_ format are listed below again for clarity: 45 | 46 | ``` 47 | F1 script-binding metadata_osd/toggleenable 48 | F5 script-binding metadata_osd/toggleautohide 49 | # script-binding metadata_osd/toggleosd_1 50 | # script-binding metadata_osd/toggleosd_2 51 | F6 script-binding metadata_osd/reset_usertoggled 52 | # script-binding metadata_osd/showstatusosd 53 | ``` 54 | 55 | ## Per media-type enable / autohide 56 | 57 | OSD enabled state or auto-hiding after a delay can be triggered either manually by pressing the relevant key (see _key_toggleenable_ and _key_toggleautohide_ [above](#key-bindings)) or determined algorithmically based on the currently playing media type and its related config options settings. 58 | 59 | OSD is enabled by default for audio and video media, disabled while viewing pictures. Autohide feature is enabled for video, autohide is disabled (that is, the OSD will stay visible) while playing music, as well as for music files with cover art image. 60 | 61 | Currently recognizable media types are namely: _audio_, _audio_withalbumart_, _video_, _image_. 62 | 63 | Config options for per media-type OSD enable and autohide are cumulatively: 64 | 65 | * _enable_for\__ (yes/no) 66 | * _autohide_for\__ (yes/no) 67 | 68 | If user presses a button to toggle enable / disable the OSD or the autohide feature, it will override the automatic determining until reset back by presssing a key specified by: 69 | 70 | * _key_reset_usertoggled_ / _reset_usertoggled_ (F6) (see [above](#key-bindings)) 71 | 72 | ## Program Architecture 73 | 74 | See [ARCHITECTURE.md](ARCHITECTURE.md). 75 | 76 | ## License 77 | 78 | This project is licensed under the terms of the **MIT license**. See [LICENSE](LICENSE). 79 | -------------------------------------------------------------------------------- /StateMachineDiagram.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vc-01/metadata-osd/c8581ea1786c8fce1cfec570c17dd07f8fafc9ea/StateMachineDiagram.dia -------------------------------------------------------------------------------- /StateMachineDiagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | -------------------------------------------------------------------------------- /recreate_userconf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) 2023 Vladimir Chren 3 | # All rights reserved. 4 | # 5 | # SPDX-License-Identifier: MIT 6 | 7 | # This script extracts default config options and its values 8 | # from metadata_osd.lua script and saves them as .conf file(s). 9 | 10 | set -e 11 | 12 | declare -r INPFILE="scripts/metadata_osd.lua" 13 | declare -r USERCONFFILE="script-opts/metadata_osd.conf" 14 | declare -r USERCONFFILE_DEFAULTS="script-opts/metadata_osd_defaults.conf" 15 | 16 | while getopts e optvar; do 17 | case "$optvar" in 18 | e) EXTRACT_DEFAULTS=1 ;; 19 | esac 20 | done 21 | 22 | cat > "$USERCONFFILE" <<~HEREDOCUMENT 23 | ## metadata_osd. Example configuration file. 24 | ## Default values are pre-filled and commented out. 25 | 26 | ~HEREDOCUMENT 27 | 28 | function extract_luaoptions() 29 | { 30 | declare -r MATCH_FROM="^local options" 31 | declare -r MATCH_TO="-- cut-here --" 32 | 33 | # Extract text between /MATCH_FROM/,/MATCH_TO/ excluding the match itself 34 | # FIXME: Don't add extra new line at the end. 35 | sed --regexp-extended -n -e " 36 | /$MATCH_FROM/{ # if equal $MATCH_FROM 37 | n; # read next line / skip current 38 | bm # branch label m(atch) 39 | }; 40 | d; # delete pattern space & restart cycle 41 | :m 42 | /$MATCH_TO/!{ # if not equal $MATCH_TO 43 | p; # print pattern space 44 | n; # read next line 45 | bm # branch m 46 | } 47 | q; # quit" \ 48 | "$INPFILE" 49 | } 50 | 51 | function reinterpret_as_userconf() 52 | { 53 | sed --regexp-extended \ 54 | -e "s/^[[:blank:]]+/#/" \ 55 | -e "s/(.*) = (.*),$/\1=\2/" \ 56 | -e "s/=false$/=no/" \ 57 | -e "s/=true$/=yes/" \ 58 | -e "s/=[\"'](.*)[\"']$/=\1/" 59 | } 60 | 61 | function extract_defaults() 62 | { 63 | sed --regexp-extended \ 64 | -e "/^## /d" \ 65 | -e "/^#-- /d" \ 66 | -e "/^$/d" \ 67 | -e "s/^#(.*)/\1/" 68 | } 69 | 70 | extract_luaoptions | 71 | if [[ -n $EXTRACT_DEFAULTS ]] 72 | then 73 | reinterpret_as_userconf | 74 | tee -a "$USERCONFFILE" | 75 | extract_defaults > "$USERCONFFILE_DEFAULTS" 76 | else 77 | reinterpret_as_userconf >> "$USERCONFFILE" 78 | fi 79 | -------------------------------------------------------------------------------- /script-opts/metadata_osd.conf: -------------------------------------------------------------------------------- 1 | ## metadata_osd. Example configuration file. 2 | ## Default values are pre-filled and commented out. 3 | 4 | #-- Enable OSD on mpv startup 5 | #enable_on_start=yes 6 | 7 | #-- Enable OSD for tracks 8 | #enable_for_audio=yes 9 | #enable_for_audio_withalbumart=yes 10 | #enable_for_video=yes 11 | #enable_for_image=no 12 | 13 | #-- Enable OSD-2 (with chapter title metadata if present) 14 | #enable_osd_2=yes 15 | 16 | #-- Enable pathname fallback 17 | #enable_pathname_fallback_dirnameup=yes 18 | #enable_pathname_fallback_dirname=yes 19 | #enable_pathname_fallback_filename=yes 20 | 21 | #-- Autohide OSD for tracks 22 | #autohide_for_audio=no 23 | #autohide_for_audio_withalbumart=no 24 | #autohide_for_video=yes 25 | #autohide_for_image=yes 26 | 27 | #-- Autohide delay in seconds 28 | #autohide_timeout_sec=5 29 | #autohide_statusosd_timeout_sec=5 30 | 31 | #-- Key bindings 32 | 33 | #-- Master enable / disable key (killswitch) 34 | #key_toggleenable=F1 35 | 36 | #-- Key to enable / disable the OSD autohide feature 37 | #key_toggleautohide=F5 38 | 39 | #-- Key to show / hide OSD-1 40 | #-- - current autohide state applies so if autohide is enabled, OSD will hide again 41 | #-- after the specified delay 42 | #key_toggleosd_1= 43 | 44 | #-- Key to show / hide OSD-2 (with chapter title metadata if present) 45 | #-- - current autohide state applies (see above) 46 | #-- - OSD-2 needs to be enabled by 'enable_osd_2' config option 47 | #-- - OSD-2 needs to have some data 48 | #key_toggleosd_2= 49 | 50 | #-- Reset any user-toggled switches 51 | #key_reset_usertoggled=F6 52 | 53 | #-- Key to show status OSD 54 | #-- - displays OSD and autohide state (enabled / disabled) 55 | #key_showstatusosd= 56 | 57 | #-- Maximum OSD message length 58 | #-- OSD messages will be trimmed after the specified (character) length. 59 | #osd_message_maxlength=96 60 | 61 | #-- Default OSD-1 layout & content: 62 | #-- ┌─────────────────┐ 63 | #-- │ padding top │ 64 | #-- ├─────────────────┤ 65 | #-- │ TEXT AREA 1 │ 66 | #-- ├─────────────────┤ 67 | #-- │ padding top │ 68 | #-- ├─────────────────┼─────────────────────┐ 69 | #-- │ TEXT AREA 2 │ TEXT AREA 2 RELDATE │ 70 | #-- ├─────────────────┼─────────────────────┘ 71 | #-- │ padding top │ 72 | #-- ├─────────────────┤ 73 | #-- │ TEXT AREA 3 │ 74 | #-- ├─────────────────┤ 75 | #-- │ padding top │ 76 | #-- ├─────────────────┤ 77 | #-- │ TEXT AREA 4 │ 78 | #-- └─────────────────┘ 79 | 80 | #-- ===================== ========================= ====================== 81 | #-- Layout Element Filled w/Metadata (for online content) 82 | #-- ===================== ========================= ====================== 83 | #-- TEXT AREA 1 Artist < empty > 84 | #-- TEXT AREA 2 Album Uploader 85 | #-- TEXT AREA 2 RELDATE Release Year < empty > 86 | #-- TEXT AREA 3 Title Media Title 87 | #-- TEXT AREA 4 Playlist Position / <-- 88 | #-- Playlist Count 89 | #-- ===================== ========================= ====================== 90 | 91 | #-- ===================== ========================= 92 | #-- Layout Element Path-name Fallback 93 | #-- ===================== ========================= 94 | #-- TEXT AREA 1 Directory name (one above) 95 | #-- TEXT AREA 2 Directory name 96 | #-- TEXT AREA 2 RELDATE 97 | #-- TEXT AREA 3 File name 98 | #-- TEXT AREA 4 99 | #-- ===================== ========================= 100 | 101 | #-- Default OSD-2 layout & content: 102 | #-- ┌─────────────────┐ 103 | #-- │ padding top │ 104 | #-- ├─────────────────┤ 105 | #-- │ TEXT AREA 1 │ 106 | #-- └─────────────────┘ 107 | 108 | #-- ================= ====================== 109 | #-- Layout Element Filled w/Metadata 110 | #-- ================= ====================== 111 | #-- TEXT AREA 1 Chapter Title 112 | #-- ================= ====================== 113 | 114 | #-- Styling options 115 | 116 | #-- Style: Padding top (in number of half-lines) 117 | #-- Allowed values are integers in range: 118 | #-- 0, 1, .. 40 119 | #style_paddingtop_osd_1_textarea_1=1 120 | #style_paddingtop_osd_1_textarea_2=0 121 | #style_paddingtop_osd_1_textarea_3=2 122 | #style_paddingtop_osd_1_textarea_4=3 123 | #style_paddingtop_osd_2_textarea_1=3 124 | 125 | #-- Style: Alignment 126 | #-- Values may be string(s) (multiple separated by semicolon ';'): 127 | #-- left_justified (or) centered (or) right_justified ';' subtitle (or) midtitle (or) toptitle 128 | #style_alignment_osd_1=left_justified;midtitle 129 | #style_alignment_osd_2=centered;midtitle 130 | 131 | #-- Style: Font style override 132 | #-- Values may be string(s) (multiple separated by semicolon ';'): 133 | #-- italic ';' bold 134 | #style_fontstyle_osd_1_textarea_1=bold 135 | #style_fontstyle_osd_1_textarea_2=bold 136 | #style_fontstyle_osd_1_textarea_2_releasedate= 137 | #style_fontstyle_osd_1_textarea_3=italic 138 | #style_fontstyle_osd_1_textarea_4= 139 | #style_fontstyle_osd_2_textarea_1= 140 | 141 | #-- Style: Border width of the outline around the text 142 | #-- Allowed values are integers: 143 | #-- 0, 1, 2, 3 or 4 144 | #style_bord_osd_1=3 145 | #style_bord_osd_2=3 146 | 147 | #-- Style: Shadow depth of the text 148 | #-- Allowed values are integers: 149 | #-- 0, 1, 2, 3 or 4 150 | #style_shad_osd_1_textarea_1=0 151 | #style_shad_osd_1_textarea_2=0 152 | #style_shad_osd_1_textarea_3=1 153 | #style_shad_osd_1_textarea_4=0 154 | #style_shad_osd_2_textarea_1=0 155 | 156 | #-- Style: Font scale (in percent) 157 | #-- Allowed values are integers in range: 158 | #-- 10, 11, .. 400 159 | #style_fsc_osd_1_textarea_1=100 160 | #style_fsc_osd_1_textarea_2=100 161 | #style_fsc_osd_1_textarea_3=100 162 | #style_fsc_osd_1_textarea_4=60 163 | #style_fsc_osd_2_textarea_1=100 164 | 165 | #-- Style: Distance between letters 166 | #-- Allowed values are integers in range: 167 | #-- 0, 1, .. 40 168 | #style_fsp_osd_1_textarea_1=0 169 | #style_fsp_osd_1_textarea_2=0 170 | #style_fsp_osd_1_textarea_3=10 171 | #style_fsp_osd_1_textarea_4=0 172 | #style_fsp_osd_2_textarea_1=0 173 | 174 | #-- Current Chapter Number 175 | 176 | #-- Show current chapter number in addition to the current playlist position. 177 | #-- Can be useful (also) for audio files with internal chapters carrying a song 178 | #-- per chapter. 179 | 180 | #-- Current playlist position if the setting is activated (see below) 181 | #-- is moved one line down and put between square brackets. 182 | #-- E.g.: 183 | #-- Chapter: 4/16 184 | #-- [1/5] <-- playlist position 185 | 186 | #-- If the chapter number is equal to the current playlist position, the value 187 | #-- is conflated with playlist position (to avoid duplicity). 188 | #-- ^ (reworded) Applied only if not equal to the current playlist position. 189 | 190 | #-- If playlist consists of exactly one media, playlist position is omitted 191 | #-- instead and substituted for chapter number. 192 | 193 | #show_chapternumber=no 194 | 195 | #-- Current Album Track Number 196 | 197 | #-- Show current album track number in addition to the (encompassing) playlist 198 | #-- position (if present in metadata). 199 | #-- Can be useful if the playlist traverses multiple directories. 200 | 201 | #-- Current playlist position if the setting is activated (see below) 202 | #-- is moved one line down and put between square brackets. 203 | #-- E.g.: 204 | #-- Track: 3 205 | #-- [4/26] <-- playlist position 206 | 207 | #-- If the track number is equal to the current playlist position, the value 208 | #-- is conflated with playlist position (to avoid duplicity). 209 | #-- ^ (reworded) Applied only if not equal to the current playlist position. 210 | 211 | #-- If playlist consists of exactly one media, playlist position is omitted 212 | #-- instead and substituted for album track number. 213 | 214 | #-- _Note_: Album track number is scarcely present in metadata, 215 | #-- this can give mixed results. 216 | 217 | #show_albumtracknumber=no 218 | 219 | #-- *** UNSTABLE OPTIONS BELOW *** 220 | #-- * Options below are still riping. They might be changed or removed 221 | #-- in the future without further notice. * 222 | 223 | #-- OSD layout 224 | 225 | #-- Overall layout for elements 226 | #tmpl_layout_osd_1={{CONTENT_TEXTAREA_1_MEDIA}}{{NEWLINE}}{{CONTENT_TEXTAREA_2_MEDIA}}{{CONTENT_TEXTAREA_2_RELDATE_MEDIA}}{{NEWLINE}}{{CONTENT_TEXTAREA_3_MEDIA}}{{NEWLINE}}{{CONTENT_TEXTAREA_4_MEDIA}} 227 | #tmpl_layout_osd_2={{CONTENT_TEXTAREA_1_MEDIA}} 228 | 229 | #-- Text area content for media type 230 | 231 | #-- _Note_: Templating syntax is as yet _unstable_, 232 | #-- it can change in the future. 233 | 234 | #-- Tag expansion: 235 | #-- Expand variable with name VAR: 236 | #-- ##VAR## 237 | #-- Conditionally include if value of VAR is a non zero length string: 238 | #-- {{#?VAR}}TEMPLATE_TEXT{{#/}} 239 | #-- The above works also as logical OR, so multiple condidions are possible: 240 | #-- {{#?VAR_1}}TEMPLATE_TEXT_1{{#?VAR_2}}TEMPLATE_TEXT_2{{#/}} 241 | 242 | #content_osd_1_textarea_1_audio={{#?ARTIST}}##ARTIST##{{#?DIRNAME_UP}}##DIRNAME_UP##{{#/}} 243 | #content_osd_1_textarea_2_audio={{#?ALBUM}}##ALBUM##{{#?DIRNAME}}##DIRNAME##{{#/}} 244 | #content_osd_1_textarea_2_reldate_audio={{#?RELEASE_YEAR}}{{UNICODE_SP}}(##RELEASE_YEAR##){{#/}} 245 | #content_osd_1_textarea_3_audio={{#?TITLE}}##TITLE##{{#?FILENAME}}##FILENAME##{{#/}} 246 | #content_osd_1_textarea_4_audio=##TEXTAREA_4_GEN## 247 | #content_osd_2_textarea_1_audio=##CHAPTERTITLE## 248 | 249 | #content_osd_1_textarea_1_audio_withalbumart={{#?ARTIST}}##ARTIST##{{#?DIRNAME_UP}}##DIRNAME_UP##{{#/}} 250 | #content_osd_1_textarea_2_audio_withalbumart={{#?ALBUM}}##ALBUM##{{#?DIRNAME}}##DIRNAME##{{#/}} 251 | #content_osd_1_textarea_2_reldate_audio_withalbumart={{#?RELEASE_YEAR}}{{UNICODE_SP}}(##RELEASE_YEAR##){{#/}} 252 | #content_osd_1_textarea_3_audio_withalbumart={{#?TITLE}}##TITLE##{{#?FILENAME}}##FILENAME##{{#/}} 253 | #content_osd_1_textarea_4_audio_withalbumart=##TEXTAREA_4_GEN## 254 | #content_osd_2_textarea_1_audio_withalbumart=##CHAPTERTITLE## 255 | 256 | #content_osd_1_textarea_1_video=##DIRNAME_UP## 257 | #content_osd_1_textarea_2_video=##DIRNAME## 258 | #content_osd_1_textarea_2_reldate_video={{#?RELEASE_YEAR}}{{UNICODE_SP}}(##RELEASE_YEAR##){{#/}} 259 | #content_osd_1_textarea_3_video={{#?TITLE}}##TITLE##{{#?FILENAME}}##FILENAME##{{#/}} 260 | #content_osd_1_textarea_4_video=##TEXTAREA_4_GEN## 261 | #content_osd_2_textarea_1_video=##CHAPTERTITLE## 262 | 263 | #content_osd_1_textarea_1_image={{#?ARTIST}}##ARTIST##{{#?DIRNAME_UP}}##DIRNAME_UP##{{#/}} 264 | #content_osd_1_textarea_2_image={{#?ALBUM}}##ALBUM##{{#?DIRNAME}}##DIRNAME##{{#/}} 265 | #content_osd_1_textarea_2_reldate_image={{#?RELEASE_YEAR}}{{UNICODE_SP}}(##RELEASE_YEAR##){{#/}} 266 | #content_osd_1_textarea_3_image={{#?TITLE}}##TITLE##{{#?FILENAME}}##FILENAME##{{#/}} 267 | #content_osd_1_textarea_4_image=##TEXTAREA_4_GEN## 268 | #content_osd_2_textarea_1_image= 269 | 270 | #content_osd_1_textarea_1_stream= 271 | #content_osd_1_textarea_2_stream=##UPLOADER## 272 | #content_osd_1_textarea_3_stream=##MEDIATITLE## 273 | #content_osd_1_textarea_4_stream=##TEXTAREA_4_GEN## 274 | #content_osd_2_textarea_1_stream=##CHAPTERTITLE## 275 | 276 | #-- Global string substitutions for pathname fallback 277 | 278 | #-- For *_gsubpatt_* options, so called Lua "patterns" apply as documented 279 | #-- in the documentation: 280 | #-- https://www.lua.org/manual/5.1/manual.html#5.4.1 281 | 282 | #-- Characters after equal sign '=' are not interpreted specially, 283 | #-- subsequent equal signs or quotes will be part of the value. 284 | 285 | #-- For *_gsubrepl_* options, value is taken as such as string replacement. 286 | #-- Optionally, space character can be inserted as %UNICODE_SP% in case of 287 | #-- a need to make it visible (e.g. if it's at the end of the string). 288 | 289 | #-- Text Area 1: Directory name one above of the currently loaded media file 290 | 291 | #-- (empty slot) 292 | #pathname_fallback_dirnameup_gsubpatt_1= 293 | #pathname_fallback_dirnameup_gsubrepl_1= 294 | #-- (empty slot) 295 | #pathname_fallback_dirnameup_gsubpatt_2= 296 | #pathname_fallback_dirnameup_gsubrepl_2= 297 | #-- (empty slot) 298 | #pathname_fallback_dirnameup_gsubpatt_3= 299 | #pathname_fallback_dirnameup_gsubrepl_3= 300 | 301 | #-- Text Area 2: Directory name of the currently loaded media file 302 | 303 | #-- (empty slot) 304 | #pathname_fallback_dirname_gsubpatt_1= 305 | #pathname_fallback_dirname_gsubrepl_1= 306 | #-- (empty slot) 307 | #pathname_fallback_dirname_gsubpatt_2= 308 | #pathname_fallback_dirname_gsubrepl_2= 309 | #-- (empty slot) 310 | #pathname_fallback_dirname_gsubpatt_3= 311 | #pathname_fallback_dirname_gsubrepl_3= 312 | 313 | #-- Text Area 3: File name without extension 314 | 315 | #-- Replace underscore(s) with space character 316 | #pathname_fallback_filename_gsubpatt_1=_+ 317 | #pathname_fallback_filename_gsubrepl_1=%UNICODE_SP% 318 | #-- Remove leading track number 319 | #pathname_fallback_filename_gsubpatt_2=^%d+%s+-%s+ 320 | #pathname_fallback_filename_gsubrepl_2= 321 | #-- (empty slot) 322 | #pathname_fallback_filename_gsubpatt_3= 323 | #pathname_fallback_filename_gsubrepl_3= 324 | 325 | -------------------------------------------------------------------------------- /scripts/metadata_osd.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | metadata_osd. Version 0.6.2 3 | 4 | Copyright (c) 2022-2023 Vladimir Chren 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | ]] 24 | 25 | local opt = require 'mp.options' 26 | local msg = require 'mp.msg' 27 | local utils = require 'mp.utils' 28 | 29 | -- defaults 30 | local options = { 31 | -- Enable OSD on mpv startup 32 | enable_on_start = true, 33 | 34 | -- Enable OSD for tracks 35 | enable_for_audio = true, 36 | enable_for_audio_withalbumart = true, 37 | enable_for_video = true, 38 | enable_for_image = false, 39 | 40 | -- Enable OSD-2 (with chapter title metadata if present) 41 | enable_osd_2 = true, 42 | 43 | -- Enable pathname fallback 44 | enable_pathname_fallback_dirnameup = true, 45 | enable_pathname_fallback_dirname = true, 46 | enable_pathname_fallback_filename = true, 47 | 48 | -- Autohide OSD for tracks 49 | autohide_for_audio = false, 50 | autohide_for_audio_withalbumart = false, 51 | autohide_for_video = true, 52 | autohide_for_image = true, 53 | 54 | -- Autohide delay in seconds 55 | autohide_timeout_sec = 5, 56 | autohide_statusosd_timeout_sec = 5, 57 | 58 | -- Key bindings 59 | 60 | -- Master enable / disable key (killswitch) 61 | key_toggleenable = 'F1', 62 | 63 | -- Key to enable / disable the OSD autohide feature 64 | key_toggleautohide = 'F5', 65 | 66 | -- Key to show / hide OSD-1 67 | -- - current autohide state applies so if autohide is enabled, OSD will hide again 68 | -- after the specified delay 69 | key_toggleosd_1 = '', 70 | 71 | -- Key to show / hide OSD-2 (with chapter title metadata if present) 72 | -- - current autohide state applies (see above) 73 | -- - OSD-2 needs to be enabled by 'enable_osd_2' config option 74 | -- - OSD-2 needs to have some data 75 | key_toggleosd_2 = '', 76 | 77 | -- Reset any user-toggled switches 78 | key_reset_usertoggled = 'F6', 79 | 80 | -- Key to show status OSD 81 | -- - displays OSD and autohide state (enabled / disabled) 82 | key_showstatusosd = '', 83 | 84 | -- Maximum OSD message length 85 | -- OSD messages will be trimmed after the specified (character) length. 86 | osd_message_maxlength = 96, 87 | 88 | -- Default OSD-1 layout & content: 89 | -- ┌─────────────────┐ 90 | -- │ padding top │ 91 | -- ├─────────────────┤ 92 | -- │ TEXT AREA 1 │ 93 | -- ├─────────────────┤ 94 | -- │ padding top │ 95 | -- ├─────────────────┼─────────────────────┐ 96 | -- │ TEXT AREA 2 │ TEXT AREA 2 RELDATE │ 97 | -- ├─────────────────┼─────────────────────┘ 98 | -- │ padding top │ 99 | -- ├─────────────────┤ 100 | -- │ TEXT AREA 3 │ 101 | -- ├─────────────────┤ 102 | -- │ padding top │ 103 | -- ├─────────────────┤ 104 | -- │ TEXT AREA 4 │ 105 | -- └─────────────────┘ 106 | 107 | -- ===================== ========================= ====================== 108 | -- Layout Element Filled w/Metadata (for online content) 109 | -- ===================== ========================= ====================== 110 | -- TEXT AREA 1 Artist < empty > 111 | -- TEXT AREA 2 Album Uploader 112 | -- TEXT AREA 2 RELDATE Release Year < empty > 113 | -- TEXT AREA 3 Title Media Title 114 | -- TEXT AREA 4 Playlist Position / <-- 115 | -- Playlist Count 116 | -- ===================== ========================= ====================== 117 | 118 | -- ===================== ========================= 119 | -- Layout Element Path-name Fallback 120 | -- ===================== ========================= 121 | -- TEXT AREA 1 Directory name (one above) 122 | -- TEXT AREA 2 Directory name 123 | -- TEXT AREA 2 RELDATE 124 | -- TEXT AREA 3 File name 125 | -- TEXT AREA 4 126 | -- ===================== ========================= 127 | 128 | -- Default OSD-2 layout & content: 129 | -- ┌─────────────────┐ 130 | -- │ padding top │ 131 | -- ├─────────────────┤ 132 | -- │ TEXT AREA 1 │ 133 | -- └─────────────────┘ 134 | 135 | -- ================= ====================== 136 | -- Layout Element Filled w/Metadata 137 | -- ================= ====================== 138 | -- TEXT AREA 1 Chapter Title 139 | -- ================= ====================== 140 | 141 | -- Styling options 142 | 143 | -- Style: Padding top (in number of half-lines) 144 | -- Allowed values are integers in range: 145 | -- 0, 1, .. 40 146 | style_paddingtop_osd_1_textarea_1 = 1, 147 | style_paddingtop_osd_1_textarea_2 = 0, 148 | style_paddingtop_osd_1_textarea_3 = 2, 149 | style_paddingtop_osd_1_textarea_4 = 3, 150 | style_paddingtop_osd_2_textarea_1 = 3, 151 | 152 | -- Style: Alignment 153 | -- Values may be string(s) (multiple separated by semicolon ';'): 154 | -- left_justified (or) centered (or) right_justified ';' subtitle (or) midtitle (or) toptitle 155 | style_alignment_osd_1 = "left_justified;midtitle", 156 | style_alignment_osd_2 = "centered;midtitle", 157 | 158 | -- Style: Font style override 159 | -- Values may be string(s) (multiple separated by semicolon ';'): 160 | -- italic ';' bold 161 | style_fontstyle_osd_1_textarea_1 = "bold", 162 | style_fontstyle_osd_1_textarea_2 = "bold", 163 | style_fontstyle_osd_1_textarea_2_releasedate = "", 164 | style_fontstyle_osd_1_textarea_3 = "italic", 165 | style_fontstyle_osd_1_textarea_4 = "", 166 | style_fontstyle_osd_2_textarea_1 = "", 167 | 168 | -- Style: Border width of the outline around the text 169 | -- Allowed values are integers: 170 | -- 0, 1, 2, 3 or 4 171 | style_bord_osd_1 = 3, 172 | style_bord_osd_2 = 3, 173 | 174 | -- Style: Shadow depth of the text 175 | -- Allowed values are integers: 176 | -- 0, 1, 2, 3 or 4 177 | style_shad_osd_1_textarea_1 = 0, 178 | style_shad_osd_1_textarea_2 = 0, 179 | style_shad_osd_1_textarea_3 = 1, 180 | style_shad_osd_1_textarea_4 = 0, 181 | style_shad_osd_2_textarea_1 = 0, 182 | 183 | -- Style: Font scale (in percent) 184 | -- Allowed values are integers in range: 185 | -- 10, 11, .. 400 186 | style_fsc_osd_1_textarea_1 = 100, 187 | style_fsc_osd_1_textarea_2 = 100, 188 | style_fsc_osd_1_textarea_3 = 100, 189 | style_fsc_osd_1_textarea_4 = 60, 190 | style_fsc_osd_2_textarea_1 = 100, 191 | 192 | -- Style: Distance between letters 193 | -- Allowed values are integers in range: 194 | -- 0, 1, .. 40 195 | style_fsp_osd_1_textarea_1 = 0, 196 | style_fsp_osd_1_textarea_2 = 0, 197 | style_fsp_osd_1_textarea_3 = 10, 198 | style_fsp_osd_1_textarea_4 = 0, 199 | style_fsp_osd_2_textarea_1 = 0, 200 | 201 | -- Current Chapter Number 202 | 203 | -- Show current chapter number in addition to the current playlist position. 204 | -- Can be useful (also) for audio files with internal chapters carrying a song 205 | -- per chapter. 206 | 207 | -- Current playlist position if the setting is activated (see below) 208 | -- is moved one line down and put between square brackets. 209 | -- E.g.: 210 | -- Chapter: 4/16 211 | -- [1/5] <-- playlist position 212 | 213 | -- If the chapter number is equal to the current playlist position, the value 214 | -- is conflated with playlist position (to avoid duplicity). 215 | -- ^ (reworded) Applied only if not equal to the current playlist position. 216 | 217 | -- If playlist consists of exactly one media, playlist position is omitted 218 | -- instead and substituted for chapter number. 219 | 220 | show_chapternumber = false, 221 | 222 | -- Current Album Track Number 223 | 224 | -- Show current album track number in addition to the (encompassing) playlist 225 | -- position (if present in metadata). 226 | -- Can be useful if the playlist traverses multiple directories. 227 | 228 | -- Current playlist position if the setting is activated (see below) 229 | -- is moved one line down and put between square brackets. 230 | -- E.g.: 231 | -- Track: 3 232 | -- [4/26] <-- playlist position 233 | 234 | -- If the track number is equal to the current playlist position, the value 235 | -- is conflated with playlist position (to avoid duplicity). 236 | -- ^ (reworded) Applied only if not equal to the current playlist position. 237 | 238 | -- If playlist consists of exactly one media, playlist position is omitted 239 | -- instead and substituted for album track number. 240 | 241 | -- _Note_: Album track number is scarcely present in metadata, 242 | -- this can give mixed results. 243 | 244 | show_albumtracknumber = false, 245 | 246 | -- *** UNSTABLE OPTIONS BELOW *** 247 | -- * Options below are still riping. They might be changed or removed 248 | -- in the future without further notice. * 249 | 250 | -- OSD layout 251 | 252 | -- Overall layout for elements 253 | tmpl_layout_osd_1 = "{{CONTENT_TEXTAREA_1_MEDIA}}{{NEWLINE}}{{CONTENT_TEXTAREA_2_MEDIA}}{{CONTENT_TEXTAREA_2_RELDATE_MEDIA}}{{NEWLINE}}{{CONTENT_TEXTAREA_3_MEDIA}}{{NEWLINE}}{{CONTENT_TEXTAREA_4_MEDIA}}", 254 | tmpl_layout_osd_2 = "{{CONTENT_TEXTAREA_1_MEDIA}}", 255 | 256 | -- Text area content for media type 257 | 258 | -- _Note_: Templating syntax is as yet _unstable_, 259 | -- it can change in the future. 260 | 261 | -- Tag expansion: 262 | -- Expand variable with name VAR: 263 | -- ##VAR## 264 | -- Conditionally include if value of VAR is a non zero length string: 265 | -- {{#?VAR}}TEMPLATE_TEXT{{#/}} 266 | -- The above works also as logical OR, so multiple condidions are possible: 267 | -- {{#?VAR_1}}TEMPLATE_TEXT_1{{#?VAR_2}}TEMPLATE_TEXT_2{{#/}} 268 | 269 | content_osd_1_textarea_1_audio = "{{#?ARTIST}}##ARTIST##{{#?DIRNAME_UP}}##DIRNAME_UP##{{#/}}", 270 | content_osd_1_textarea_2_audio = "{{#?ALBUM}}##ALBUM##{{#?DIRNAME}}##DIRNAME##{{#/}}", 271 | content_osd_1_textarea_2_reldate_audio = "{{#?RELEASE_YEAR}}{{UNICODE_SP}}(##RELEASE_YEAR##){{#/}}", 272 | content_osd_1_textarea_3_audio = "{{#?TITLE}}##TITLE##{{#?FILENAME}}##FILENAME##{{#/}}", 273 | content_osd_1_textarea_4_audio = "##TEXTAREA_4_GEN##", 274 | content_osd_2_textarea_1_audio = "##CHAPTERTITLE##", 275 | 276 | content_osd_1_textarea_1_audio_withalbumart = "{{#?ARTIST}}##ARTIST##{{#?DIRNAME_UP}}##DIRNAME_UP##{{#/}}", 277 | content_osd_1_textarea_2_audio_withalbumart = "{{#?ALBUM}}##ALBUM##{{#?DIRNAME}}##DIRNAME##{{#/}}", 278 | content_osd_1_textarea_2_reldate_audio_withalbumart = "{{#?RELEASE_YEAR}}{{UNICODE_SP}}(##RELEASE_YEAR##){{#/}}", 279 | content_osd_1_textarea_3_audio_withalbumart = "{{#?TITLE}}##TITLE##{{#?FILENAME}}##FILENAME##{{#/}}", 280 | content_osd_1_textarea_4_audio_withalbumart = "##TEXTAREA_4_GEN##", 281 | content_osd_2_textarea_1_audio_withalbumart = "##CHAPTERTITLE##", 282 | 283 | content_osd_1_textarea_1_video = "##DIRNAME_UP##", 284 | content_osd_1_textarea_2_video = "##DIRNAME##", 285 | content_osd_1_textarea_2_reldate_video = "{{#?RELEASE_YEAR}}{{UNICODE_SP}}(##RELEASE_YEAR##){{#/}}", 286 | content_osd_1_textarea_3_video = "{{#?TITLE}}##TITLE##{{#?FILENAME}}##FILENAME##{{#/}}", 287 | content_osd_1_textarea_4_video = "##TEXTAREA_4_GEN##", 288 | content_osd_2_textarea_1_video = "##CHAPTERTITLE##", 289 | 290 | content_osd_1_textarea_1_image = "{{#?ARTIST}}##ARTIST##{{#?DIRNAME_UP}}##DIRNAME_UP##{{#/}}", 291 | content_osd_1_textarea_2_image = "{{#?ALBUM}}##ALBUM##{{#?DIRNAME}}##DIRNAME##{{#/}}", 292 | content_osd_1_textarea_2_reldate_image = "{{#?RELEASE_YEAR}}{{UNICODE_SP}}(##RELEASE_YEAR##){{#/}}", 293 | content_osd_1_textarea_3_image = "{{#?TITLE}}##TITLE##{{#?FILENAME}}##FILENAME##{{#/}}", 294 | content_osd_1_textarea_4_image = "##TEXTAREA_4_GEN##", 295 | content_osd_2_textarea_1_image = "", 296 | 297 | content_osd_1_textarea_1_stream = "", 298 | content_osd_1_textarea_2_stream = "##UPLOADER##", 299 | content_osd_1_textarea_3_stream = "##MEDIATITLE##", 300 | content_osd_1_textarea_4_stream = "##TEXTAREA_4_GEN##", 301 | content_osd_2_textarea_1_stream = "##CHAPTERTITLE##", 302 | 303 | -- Global string substitutions for pathname fallback 304 | 305 | -- For *_gsubpatt_* options, so called Lua "patterns" apply as documented 306 | -- in the documentation: 307 | -- https://www.lua.org/manual/5.1/manual.html#5.4.1 308 | 309 | -- Characters after equal sign '=' are not interpreted specially, 310 | -- subsequent equal signs or quotes will be part of the value. 311 | 312 | -- For *_gsubrepl_* options, value is taken as such as string replacement. 313 | -- Optionally, space character can be inserted as %UNICODE_SP% in case of 314 | -- a need to make it visible (e.g. if it's at the end of the string). 315 | 316 | -- Text Area 1: Directory name one above of the currently loaded media file 317 | 318 | -- (empty slot) 319 | pathname_fallback_dirnameup_gsubpatt_1 = "", 320 | pathname_fallback_dirnameup_gsubrepl_1 = "", 321 | -- (empty slot) 322 | pathname_fallback_dirnameup_gsubpatt_2 = "", 323 | pathname_fallback_dirnameup_gsubrepl_2 = "", 324 | -- (empty slot) 325 | pathname_fallback_dirnameup_gsubpatt_3 = "", 326 | pathname_fallback_dirnameup_gsubrepl_3 = "", 327 | 328 | -- Text Area 2: Directory name of the currently loaded media file 329 | 330 | -- (empty slot) 331 | pathname_fallback_dirname_gsubpatt_1 = "", 332 | pathname_fallback_dirname_gsubrepl_1 = "", 333 | -- (empty slot) 334 | pathname_fallback_dirname_gsubpatt_2 = "", 335 | pathname_fallback_dirname_gsubrepl_2 = "", 336 | -- (empty slot) 337 | pathname_fallback_dirname_gsubpatt_3 = "", 338 | pathname_fallback_dirname_gsubrepl_3 = "", 339 | 340 | -- Text Area 3: File name without extension 341 | 342 | -- Replace underscore(s) with space character 343 | pathname_fallback_filename_gsubpatt_1 = "_+", 344 | pathname_fallback_filename_gsubrepl_1 = "%UNICODE_SP%", 345 | -- Remove leading track number 346 | pathname_fallback_filename_gsubpatt_2 = "^%d+%s+-%s+", 347 | pathname_fallback_filename_gsubrepl_2 = "", 348 | -- (empty slot) 349 | pathname_fallback_filename_gsubpatt_3 = "", 350 | pathname_fallback_filename_gsubrepl_3 = "", 351 | 352 | -- cut-here -- 353 | -- FIXME: Remove options below on next release. 354 | -- Enable pathname fallback for text area 355 | enable_pathname_fallback_textarea_1 = true, 356 | enable_pathname_fallback_textarea_2 = true, 357 | enable_pathname_fallback_textarea_3 = true, 358 | -- Enable pathname fallback 359 | enable_pathname_fallback_dirname_up = true, 360 | } 361 | 362 | opt.read_options(options) 363 | 364 | local state = { 365 | SHOWING_OSD_1 = 1, 366 | SHOWING_OSD_2 = 2, 367 | OSD_HIDDEN = 3, 368 | } 369 | 370 | local mediatype = { 371 | AUDIO = "audio", 372 | AUDIO_WITHALBUMART = "audio_withalbumart", 373 | VIDEO = "video", 374 | IMAGE = "image", 375 | STREAM = "stream", 376 | } 377 | 378 | local pathname_fallback_type = { 379 | DIRNAMEUP = "dirnameup", 380 | DIRNAME = "dirname", 381 | FILENAME = "filename", 382 | } 383 | 384 | local osd_enabled = false 385 | local osd_autohide = false 386 | local osd_enabled_usertoggled = false 387 | local osd_autohide_usertoggled = false 388 | local curr_mediatype = nil 389 | local curr_state = state.OSD_HIDDEN 390 | local osd_overlay_osd_1 = mp.create_osd_overlay("ass-events") 391 | local osd_overlay_osd_2 = mp.create_osd_overlay("ass-events") 392 | local osd_timer -- forward declaration 393 | local pathname_fallback_gsubtable = {} 394 | local ellipsis_str = "..." -- unicode notation for ellipsis "\u{2026}" works in 395 | -- LuaJIT and since Lua5.3 which is not broadly 396 | -- available yet. 397 | 398 | -- String helper functions 399 | 400 | local function utf8_nextcharoffs(u_b1, u_b2, u_b3, u_b4) 401 | local nextcharoffs = nil 402 | 403 | --[[ 404 | The built-in Lua5.1 byte-oriented string functions are limited, 405 | for variable length encoded characters present in the UTF-8 406 | encoding standard, insufficient. 407 | 408 | Until LuaJIT has compile support for Lua5.3, coming with its own 409 | native UTF-8 string library, a custom implementation deems to be 410 | necessary. 411 | ]] 412 | 413 | --[[ 414 | UTF-8 Byte Sequences: Unicode Version 15.0.0, Section 3.9, Table 3-7. 415 | The Unicode Consortium. The Unicode Standard, Version 15.0.0, (Mountain View, CA: The Unicode Consortium, 2022. ISBN 978-1-936213-32-0) 416 | https://www.unicode.org/versions/Unicode15.0.0/ 417 | ]] 418 | 419 | -- U+0000..U+007F 420 | if type(u_b1) == "number" 421 | then 422 | if u_b1 >= 0x00 and u_b1 <= 0x7F 423 | then 424 | nextcharoffs = 1 425 | -- U+0080..U+07FF 426 | elseif u_b1 >= 0xC2 and u_b1 <= 0xDF 427 | then 428 | if type(u_b2) == "number" 429 | then 430 | if u_b2 >= 0x80 and u_b2 <= 0xBF 431 | then 432 | nextcharoffs = 2 433 | end 434 | end 435 | -- U+0800..U+0FFF 436 | elseif u_b1 == 0xE0 437 | then 438 | if type(u_b2) == "number" 439 | then 440 | if u_b2 >= 0xA0 and u_b2 <= 0xBF 441 | then 442 | if type(u_b3) == "number" 443 | then 444 | if u_b3 >= 0x80 and u_b3 <= 0xBF 445 | then 446 | nextcharoffs = 3 447 | end 448 | end 449 | end 450 | end 451 | -- U+1000..U+CFFF 452 | elseif u_b1 >= 0xE1 and u_b1 <= 0xEC 453 | then 454 | if type(u_b2) == "number" 455 | then 456 | if u_b2 >= 0x80 and u_b2 <= 0xBF 457 | then 458 | if type(u_b3) == "number" 459 | then 460 | if u_b3 >= 0x80 and u_b3 <= 0xBF 461 | then 462 | nextcharoffs = 3 463 | end 464 | end 465 | end 466 | end 467 | -- U+D000..U+D7FF 468 | elseif u_b1 == 0xED 469 | then 470 | if type(u_b2) == "number" 471 | then 472 | if u_b2 >= 0x80 and u_b2 <= 0x9F 473 | then 474 | if type(u_b3) == "number" 475 | then 476 | if u_b3 >= 0x80 and u_b3 <= 0xBF 477 | then 478 | nextcharoffs = 3 479 | end 480 | end 481 | end 482 | end 483 | -- U+E000..U+FFFF 484 | elseif u_b1 >= 0xEE and u_b1 <= 0xEF 485 | then 486 | if type(u_b2) == "number" 487 | then 488 | if u_b2 >= 0x80 and u_b2 <= 0xBF 489 | then 490 | if type(u_b3) == "number" 491 | then 492 | if u_b3 >= 0x80 and u_b3 <= 0xBF 493 | then 494 | nextcharoffs = 3 495 | end 496 | end 497 | end 498 | end 499 | -- U+10000..U+3FFFF 500 | elseif u_b1 == 0xF0 501 | then 502 | if type(u_b2) == "number" 503 | then 504 | if u_b2 >= 0x90 and u_b2 <= 0xBF 505 | then 506 | if type(u_b3) == "number" 507 | then 508 | if u_b3 >= 0x80 and u_b3 <= 0xBF 509 | then 510 | if type(u_b4) == "number" 511 | then 512 | if u_b4 >= 0x80 and u_b4 <= 0xBF 513 | then 514 | nextcharoffs = 4 515 | end 516 | end 517 | end 518 | end 519 | end 520 | end 521 | -- U+40000..U+FFFFF 522 | elseif u_b1 >= 0xF1 and u_b1 <= 0xF3 523 | then 524 | if type(u_b2) == "number" 525 | then 526 | if u_b2 >= 0x80 and u_b2 <= 0xBF 527 | then 528 | if type(u_b3) == "number" 529 | then 530 | if u_b3 >= 0x80 and u_b3 <= 0xBF 531 | then 532 | if type(u_b4) == "number" 533 | then 534 | if u_b4 >= 0x80 and u_b4 <= 0xBF 535 | then 536 | nextcharoffs = 4 537 | end 538 | end 539 | end 540 | end 541 | end 542 | end 543 | -- U+100000..U+10FFFF 544 | elseif u_b1 == 0xF4 545 | then 546 | if type(u_b2) == "number" 547 | then 548 | if u_b2 >= 0x80 and u_b2 <= 0x8F 549 | then 550 | if type(u_b3) == "number" 551 | then 552 | if u_b3 >= 0x80 and u_b3 <= 0xBF 553 | then 554 | if type(u_b4) == "number" 555 | then 556 | if u_b4 >= 0x80 and u_b4 <= 0xBF 557 | then 558 | nextcharoffs = 4 559 | end 560 | end 561 | end 562 | end 563 | end 564 | end 565 | end 566 | end 567 | 568 | return nextcharoffs 569 | end 570 | 571 | local function str_isempty(arg) 572 | return type(arg) ~= "string" or string.len(arg) == 0 573 | end 574 | 575 | local function str_isnonempty(arg) 576 | return type(arg) == "string" and string.len(arg) > 0 577 | end 578 | 579 | local function str_trunc(str) 580 | local result = str 581 | local str_truncpos = options.osd_message_maxlength 582 | 583 | if str_isnonempty(str) 584 | then 585 | str = string.gsub(str, "[\r\n]", "") 586 | if str_truncpos > 0 587 | then 588 | local str_bytepos = 1 589 | local str_charcount = 0 590 | local nextcharoffs = nil 591 | local u_b1, u_b2, u_b3, u_b4 = nil, nil, nil, nil 592 | 593 | repeat 594 | u_b1, u_b2, u_b3, u_b4 = 595 | string.byte(str, str_bytepos, str_bytepos + 4) 596 | nextcharoffs = 597 | utf8_nextcharoffs( 598 | u_b1, u_b2, u_b3, u_b4) 599 | if nextcharoffs 600 | then 601 | str_charcount = str_charcount + 1 602 | str_bytepos = str_bytepos + nextcharoffs 603 | end 604 | until nextcharoffs == nil or 605 | str_charcount >= options.osd_message_maxlength 606 | 607 | if u_b1 == nil and nextcharoffs == nil -- reached end of string 608 | then 609 | return result 610 | elseif u_b1 ~= nil and nextcharoffs == nil -- found invalid utf-8 char 611 | then 612 | msg.debug("str_trunc(): found invalid UTF-8 character; falling back to byte-oriented string truncate.") 613 | elseif u_b1 ~= nil and nextcharoffs ~= nil -- string needs to be trunc-ed 614 | then 615 | str_truncpos = str_bytepos - 1 616 | end 617 | end 618 | 619 | if string.len(str) > str_truncpos 620 | then 621 | result = 622 | string.sub(str, 1, str_truncpos) .. 623 | ellipsis_str 624 | end 625 | end 626 | 627 | return result 628 | end 629 | 630 | local function bool2enabled_str(arg) 631 | local result = "Disabled" 632 | 633 | if type(arg) == "boolean" and arg then 634 | result = "Enabled" 635 | end 636 | 637 | return result 638 | end 639 | 640 | local function str_split(s, split_char) 641 | local token_pass_1 = nil 642 | local token_pass_2 = nil 643 | local res_t = {} 644 | 645 | if str_isnonempty(s) and str_isnonempty(split_char) and split_char:len() == 1 646 | then 647 | for token_pass_1 in string.gmatch(s, '([^' .. split_char .. ']+)') 648 | do 649 | token_pass_2 = 650 | string.match( 651 | token_pass_1, '^[%s]*(.-)[%s]*$') 652 | 653 | if str_isnonempty(token_pass_2) 654 | then 655 | msg.trace("str_split(): new token: \"" .. token_pass_2 .. "\"") 656 | res_t[token_pass_2] = true 657 | end 658 | end 659 | end 660 | 661 | return res_t 662 | end 663 | 664 | local function str_split_styleoption(styleopt_str) 665 | return str_split(styleopt_str, ';') 666 | end 667 | 668 | local function parse_styleoption_int_inrange(styleopt_int, range_min, range_max) 669 | local styleopt_int = tonumber(styleopt_int) 670 | local res = nil 671 | 672 | if styleopt_int 673 | then 674 | styleopt_int = math.floor(styleopt_int) 675 | 676 | if styleopt_int >= range_min and styleopt_int <= range_max 677 | then 678 | res = styleopt_int 679 | end 680 | end 681 | 682 | return res 683 | end 684 | 685 | local function parse_styleoption_alignment(styleopt_alignment) 686 | local ass_alignment_leftjustified = 1 687 | local ass_alignment_centered = 2 688 | local ass_alignment_rightjustified = 3 689 | local ass_alignment_subtitle = 0 690 | local ass_alignment_toptitle = 4 691 | local ass_alignment_midtitle = 8 692 | 693 | local opt_t = 694 | str_split_styleoption( 695 | styleopt_alignment) 696 | 697 | local alignment_justification = 698 | ass_alignment_centered 699 | 700 | if opt_t["left_justified"] 701 | then 702 | alignment_justification = 703 | ass_alignment_leftjustified 704 | elseif opt_t["centered"] 705 | then 706 | alignment_justification = 707 | ass_alignment_centered 708 | elseif opt_t["right_justified"] 709 | then 710 | alignment_justification = 711 | ass_alignment_rightjustified 712 | end 713 | 714 | local alignment_position = 715 | ass_alignment_midtitle 716 | 717 | if opt_t["subtitle"] 718 | then 719 | alignment_position = 720 | ass_alignment_subtitle 721 | elseif opt_t["toptitle"] 722 | then 723 | alignment_position = 724 | ass_alignment_toptitle 725 | elseif opt_t["midtitle"] 726 | then 727 | alignment_position = 728 | ass_alignment_midtitle 729 | end 730 | 731 | return 732 | "{\\a" .. 733 | tostring(alignment_justification + alignment_position) .. 734 | "}" 735 | end 736 | 737 | local function parse_styleoption_bord(styleopt_bord) 738 | local res_bord = 0 739 | local styleopt_bord = 740 | parse_styleoption_int_inrange(styleopt_bord, 0, 4) 741 | 742 | if styleopt_bord 743 | then 744 | res_bord = styleopt_bord 745 | end 746 | 747 | return 748 | "{\\bord" .. 749 | tostring(res_bord) .. 750 | "}" 751 | end 752 | 753 | local function parse_styleoption_paddingtop(styleopt_paddingtop) 754 | local res_paddingtop = 0 755 | local styleopt_paddingtop = 756 | parse_styleoption_int_inrange(styleopt_paddingtop, 0, 40) 757 | 758 | if styleopt_paddingtop 759 | then 760 | res_paddingtop = styleopt_paddingtop 761 | end 762 | 763 | return 764 | string.rep("\\N", res_paddingtop) 765 | end 766 | 767 | local function parse_styleoption_shad(styleopt_shad) 768 | local res_shad = 0 769 | local styleopt_shad = 770 | parse_styleoption_int_inrange(styleopt_shad, 0, 4) 771 | 772 | if styleopt_shad 773 | then 774 | res_shad = styleopt_shad 775 | end 776 | 777 | return 778 | "{\\shad" .. 779 | tostring(res_shad) .. 780 | "}" 781 | end 782 | 783 | local function parse_styleoption_fsc(styleopt_fsc) 784 | local res_fsc = 100 785 | local styleopt_fsc = 786 | parse_styleoption_int_inrange(styleopt_fsc, 10, 400) 787 | 788 | if styleopt_fsc 789 | then 790 | res_fsc = styleopt_fsc 791 | end 792 | 793 | return 794 | "{\\fscx" .. 795 | tostring(res_fsc) .. 796 | "}" .. 797 | "{\\fscy" .. 798 | tostring(res_fsc) .. 799 | "}" 800 | end 801 | 802 | local function parse_styleoption_fsp(styleopt_fsp) 803 | local res_fsp = 0 804 | local styleopt_fsp = 805 | parse_styleoption_int_inrange(styleopt_fsp, 0, 40) 806 | 807 | if styleopt_fsp 808 | then 809 | res_fsp = styleopt_fsp 810 | end 811 | 812 | return 813 | "{\\fsp" .. 814 | tostring(res_fsp) .. 815 | "}" 816 | end 817 | 818 | local function parse_styleoption_fontstyle(styleopt_fontstyle) 819 | local opt_t = 820 | str_split_styleoption( 821 | styleopt_fontstyle) 822 | local italic, bold = false, false 823 | 824 | if opt_t["italic"] 825 | then 826 | italic = true 827 | end 828 | 829 | if opt_t["bold"] 830 | then 831 | bold = true 832 | end 833 | 834 | return italic, bold 835 | end 836 | 837 | local function parse_style_options() 838 | local osd_1_textarea_count = 4 -- for textarea_* loops 839 | 840 | -- prepare empty table variables 841 | local ass_style = {} 842 | ass_style.osd_1 = {} 843 | for i = 1, osd_1_textarea_count 844 | do 845 | ass_style.osd_1["textarea_" .. tostring(i)] = {} 846 | end 847 | ass_style.osd_1.textarea_2_reldate = {} 848 | ass_style.osd_2 = {} 849 | ass_style.osd_2.textarea_1 = {} 850 | 851 | -- Style: Alignment 852 | ass_style.osd_1.alignment = 853 | parse_styleoption_alignment( 854 | options.style_alignment_osd_1) 855 | 856 | ass_style.osd_2.alignment = 857 | parse_styleoption_alignment( 858 | options.style_alignment_osd_2) 859 | 860 | -- Style: Border width of the outline around the text 861 | ass_style.osd_1.bord = 862 | parse_styleoption_bord( 863 | options.style_bord_osd_1) 864 | 865 | ass_style.osd_2.bord = 866 | parse_styleoption_bord( 867 | options.style_bord_osd_2) 868 | 869 | -- Style: Font style 870 | for i = 1, osd_1_textarea_count 871 | do 872 | ass_style.osd_1["textarea_" .. tostring(i)].fontstyle = {} 873 | ass_style.osd_1["textarea_" .. tostring(i)].fontstyle.is_italic, 874 | ass_style.osd_1["textarea_" .. tostring(i)].fontstyle.is_bold = 875 | parse_styleoption_fontstyle( 876 | options["style_fontstyle_osd_1_textarea_" .. tostring(i)]) 877 | end 878 | 879 | ass_style.osd_1.textarea_2_reldate.fontstyle = {} 880 | ass_style.osd_1.textarea_2_reldate.fontstyle.is_italic, 881 | ass_style.osd_1.textarea_2_reldate.fontstyle.is_bold = 882 | parse_styleoption_fontstyle( 883 | options.style_fontstyle_osd_1_textarea_2_releasedate) 884 | 885 | ass_style.osd_2.textarea_1.fontstyle = {} 886 | ass_style.osd_2.textarea_1.fontstyle.is_italic, 887 | ass_style.osd_2.textarea_1.fontstyle.is_bold = 888 | parse_styleoption_fontstyle( 889 | options.style_fontstyle_osd_2_textarea_1) 890 | 891 | -- Style: Padding top 892 | for i = 1, osd_1_textarea_count 893 | do 894 | ass_style.osd_1["textarea_" .. tostring(i)].paddingtop = 895 | parse_styleoption_paddingtop( 896 | options["style_paddingtop_osd_1_textarea_" .. tostring(i)]) 897 | end 898 | 899 | ass_style.osd_1.textarea_2_reldate.paddingtop = "" 900 | 901 | ass_style.osd_2.textarea_1.paddingtop = 902 | parse_styleoption_paddingtop( 903 | options.style_paddingtop_osd_2_textarea_1) 904 | 905 | -- Style: Shadow depth of the text 906 | for i = 1, osd_1_textarea_count 907 | do 908 | ass_style.osd_1["textarea_" .. tostring(i)].shad = 909 | parse_styleoption_shad( 910 | options["style_shad_osd_1_textarea_" .. tostring(i)]) 911 | end 912 | 913 | ass_style.osd_1.textarea_2_reldate.shad = 914 | parse_styleoption_shad( 915 | options.style_shad_osd_1_textarea_2) 916 | 917 | ass_style.osd_2.textarea_1.shad = 918 | parse_styleoption_shad( 919 | options.style_shad_osd_2_textarea_1) 920 | 921 | -- Style: Font scale in percent 922 | for i = 1, osd_1_textarea_count 923 | do 924 | ass_style.osd_1["textarea_" .. tostring(i)].fsc = 925 | parse_styleoption_fsc( 926 | options["style_fsc_osd_1_textarea_" .. tostring(i)]) 927 | end 928 | 929 | ass_style.osd_1.textarea_2_reldate.fsc = 930 | parse_styleoption_fsc( 931 | options.style_fsc_osd_1_textarea_2) 932 | 933 | ass_style.osd_2.textarea_1.fsc = 934 | parse_styleoption_fsc( 935 | options.style_fsc_osd_2_textarea_1) 936 | 937 | -- Style: Distance between letters 938 | for i = 1, osd_1_textarea_count 939 | do 940 | ass_style.osd_1["textarea_" .. tostring(i)].fsp = 941 | parse_styleoption_fsp( 942 | options["style_fsp_osd_1_textarea_" .. tostring(i)]) 943 | end 944 | 945 | ass_style.osd_1.textarea_2_reldate.fsp = 946 | parse_styleoption_fsp( 947 | options.style_fsp_osd_1_textarea_2) 948 | 949 | ass_style.osd_2.textarea_1.fsp = 950 | parse_styleoption_fsp( 951 | options.style_fsp_osd_2_textarea_1) 952 | 953 | return ass_style 954 | end 955 | 956 | local function prepare_pathname_fallback_gsubtable() 957 | -- num. of global "pattern" substitution slots in user options 958 | local slotmax = { 959 | [pathname_fallback_type.DIRNAMEUP] = 960 | options.enable_pathname_fallback_dirnameup and 3 or 0, 961 | [pathname_fallback_type.DIRNAME] = 962 | options.enable_pathname_fallback_dirname and 3 or 0, 963 | [pathname_fallback_type.FILENAME] = 964 | options.enable_pathname_fallback_filename and 3 or 0, 965 | } 966 | 967 | for fallback_type, slotmax in pairs(slotmax) 968 | do 969 | pathname_fallback_gsubtable[fallback_type] = {} 970 | local useroptname_prefix = "pathname_fallback_" .. fallback_type 971 | for gsub_idx = 1, slotmax 972 | do 973 | local key = options[useroptname_prefix .. "_gsubpatt_" .. gsub_idx] 974 | local val = options[useroptname_prefix .. "_gsubrepl_" .. gsub_idx] 975 | val = val:gsub("%%UNICODE_SP%%", " ") 976 | if str_isnonempty(key) and val 977 | then 978 | pathname_fallback_gsubtable[fallback_type][gsub_idx] = 979 | { [key] = val } 980 | end 981 | end 982 | end 983 | 984 | msg.trace( 985 | "prepare_pathname_fallback_gsubtable(): " .. 986 | utils.to_string(pathname_fallback_gsubtable)) 987 | end 988 | 989 | local function pathname_fallback_gsubloop(fallback_type, s) 990 | for _, t in pairs(pathname_fallback_gsubtable[fallback_type]) 991 | do 992 | for patt, repl in pairs(t) 993 | do 994 | msg.debug( 995 | "pathname_fallback_gsubloop(): " .. 996 | string.format("%-9s", fallback_type) .. ": " .. 997 | "<-- \"" .. s .. "\"") 998 | s = s:gsub(patt, repl) 999 | msg.debug( 1000 | "pathname_fallback_gsubloop(): " .. 1001 | string.format("%-9s", fallback_type) .. ": " .. 1002 | "patt \"" .. patt .. "\", repl \"" .. repl .. "\"") 1003 | msg.debug( 1004 | "pathname_fallback_gsubloop(): " .. 1005 | string.format("%-9s", fallback_type) .. ": " .. 1006 | "--> \"" .. s .. "\"") 1007 | end 1008 | end 1009 | 1010 | return s 1011 | end 1012 | 1013 | -- SSA/ASS helper functions 1014 | -- spec. url: http://www.tcax.org/docs/ass-specs.htm 1015 | 1016 | local ass_tmpl = { 1017 | osd_1 = { 1018 | curr_tmpl = nil, 1019 | tokens_media = { 1020 | audio = nil, 1021 | audio_withalbumart = nil, 1022 | video = nil, 1023 | image = nil, 1024 | stream = nil, 1025 | } 1026 | }, 1027 | osd_2 = { 1028 | curr_tmpl = nil, 1029 | tokens_media = { 1030 | audio = nil, 1031 | audio_withalbumart = nil, 1032 | video = nil, 1033 | image = nil, 1034 | stream = nil, 1035 | } 1036 | }, 1037 | } 1038 | 1039 | local tmpl_token_type = { 1040 | TEXT = "_text_", 1041 | COND_OR = "_cond_or_", 1042 | COND_OR_END = "_cond_or_end_", 1043 | } 1044 | 1045 | local function ass_styleoverride_fontstyle(italic, bold, str) 1046 | res = "" 1047 | 1048 | if italic 1049 | then 1050 | res = res .. 1051 | "{\\i1}" 1052 | end 1053 | 1054 | if bold 1055 | then 1056 | res = res .. 1057 | "{\\b1}" 1058 | end 1059 | 1060 | res = res .. 1061 | str 1062 | 1063 | if bold 1064 | then 1065 | res = res .. 1066 | "{\\b0}" 1067 | end 1068 | 1069 | if italic 1070 | then 1071 | res = res .. 1072 | "{\\i0}" 1073 | end 1074 | 1075 | return res 1076 | end 1077 | 1078 | local function ass_newline() 1079 | return "\\N" 1080 | end 1081 | 1082 | local function ass_prepare_template_textarea(ass_style_textarea, content_textarea) 1083 | res = "" 1084 | 1085 | if type(ass_style_textarea) == "table" and 1086 | str_isnonempty(content_textarea) 1087 | then 1088 | res = 1089 | ass_style_textarea.shad .. 1090 | ass_style_textarea.fsc .. 1091 | ass_style_textarea.fsp .. 1092 | ass_style_textarea.paddingtop .. 1093 | ass_styleoverride_fontstyle( 1094 | ass_style_textarea.fontstyle.is_italic, 1095 | ass_style_textarea.fontstyle.is_bold, 1096 | content_textarea) .. 1097 | "{\\fsp0}" .. 1098 | "{\\fscx100}" .. 1099 | "{\\fscy100}" .. 1100 | "{\\shad0}" 1101 | end 1102 | 1103 | return res 1104 | end 1105 | 1106 | local function ass_prepare_templates() 1107 | -- sharing ASS/SSA style override code mark '{' with our templates 1108 | 1109 | local ass_style = 1110 | parse_style_options() 1111 | 1112 | local textarea_count = { -- for textarea_* loops 1113 | osd_1 = 4, 1114 | osd_2 = 1, 1115 | } 1116 | local ass_tmpl_layout = { 1117 | osd_1 = options.tmpl_layout_osd_1, 1118 | osd_2 = options.tmpl_layout_osd_2, 1119 | } 1120 | local ass_tmpl_media = { 1121 | osd_1 = {}, 1122 | osd_2 = {}, 1123 | } 1124 | 1125 | for osd_n, ass_tmpl_layout in pairs(ass_tmpl_layout) 1126 | do 1127 | for _, mediatype_ in pairs(mediatype) 1128 | do 1129 | local ass_tmpl = 1130 | ass_style[osd_n].alignment .. 1131 | ass_style[osd_n].bord .. 1132 | ass_tmpl_layout 1133 | 1134 | for i = 1, textarea_count[osd_n] 1135 | do 1136 | local tmpl_strid_textarea = 1137 | "{{CONTENT_TEXTAREA_" .. tostring(i) .. "_MEDIA}}" 1138 | 1139 | local ass_style_textarea = 1140 | ass_style[osd_n]["textarea_" .. tostring(i)] 1141 | 1142 | local content_textarea = 1143 | options["content_" .. 1144 | osd_n .. "_" .. 1145 | "textarea_" .. 1146 | tostring(i) .. "_" .. 1147 | mediatype_] 1148 | 1149 | local ass_tmpl_textarea = 1150 | ass_prepare_template_textarea( 1151 | ass_style_textarea, 1152 | content_textarea) 1153 | 1154 | ass_tmpl = 1155 | string.gsub( 1156 | ass_tmpl, 1157 | tmpl_strid_textarea, 1158 | ass_tmpl_textarea) 1159 | end 1160 | 1161 | if osd_n == "osd_1" -- remnant of bad original design 1162 | then 1163 | ass_tmpl = 1164 | string.gsub( 1165 | ass_tmpl, 1166 | "{{CONTENT_TEXTAREA_2_RELDATE_MEDIA}}", 1167 | ass_prepare_template_textarea( 1168 | ass_style[osd_n].textarea_2_reldate, 1169 | options["content_" .. 1170 | osd_n .. 1171 | "_textarea_2_reldate_" .. 1172 | mediatype_])) 1173 | end 1174 | 1175 | ass_tmpl = 1176 | string.gsub( 1177 | ass_tmpl, 1178 | "{{NEWLINE}}", 1179 | ass_newline()) 1180 | 1181 | ass_tmpl = 1182 | string.gsub( 1183 | ass_tmpl, 1184 | "{{UNICODE_SP}}", 1185 | " ") 1186 | 1187 | ass_tmpl_media[osd_n][mediatype_] = ass_tmpl 1188 | 1189 | msg.trace( 1190 | "ass_prepare_templates(): tmpl_media " .. 1191 | "(" .. osd_n .. "/" .. mediatype_ .. "): " .. 1192 | ass_tmpl) 1193 | end 1194 | end 1195 | 1196 | for _, mediatype_ in pairs(mediatype) 1197 | do 1198 | for osd_n, ass_tmpl_media in pairs(ass_tmpl_media) 1199 | do 1200 | local tmpl = ass_tmpl_media[mediatype_] 1201 | local token = nil 1202 | local tmpl_tokens = {} 1203 | local curr_pos = 1 1204 | 1205 | repeat 1206 | local curr_token_type = tmpl_token_type.TEXT 1207 | local idx_section_start, 1208 | idx_section_end, 1209 | section_op, 1210 | section_var, 1211 | section_text = 1212 | string.find(tmpl, "{{#([?])(.-)}}(.-){{#/}}", curr_pos) 1213 | 1214 | if idx_section_start and idx_section_end 1215 | and section_op and section_var and section_text 1216 | then 1217 | if idx_section_start ~= curr_pos 1218 | then 1219 | token = { 1220 | token_type = 1221 | curr_token_type, 1222 | token_text = 1223 | string.sub(tmpl, curr_pos, idx_section_start - 1) 1224 | } 1225 | table.insert(tmpl_tokens, token) 1226 | end 1227 | 1228 | if section_op == "?" 1229 | then 1230 | curr_token_type = tmpl_token_type.COND_OR 1231 | 1232 | for section_text, next_section_var in string.gmatch(section_text .. "{{#?}}", "(.-){{#[?](.-)}}") 1233 | do 1234 | token = { 1235 | token_type = 1236 | curr_token_type, 1237 | token_var = 1238 | section_var, 1239 | token_text = 1240 | section_text 1241 | } 1242 | section_var = next_section_var 1243 | table.insert(tmpl_tokens, token) 1244 | end 1245 | 1246 | token = { 1247 | token_type = 1248 | tmpl_token_type.COND_OR_END, 1249 | token_var = 1250 | nil, 1251 | token_text = 1252 | nil 1253 | } 1254 | table.insert(tmpl_tokens, token) 1255 | end 1256 | 1257 | curr_pos = idx_section_end + 1 1258 | else 1259 | token = { 1260 | token_type = 1261 | curr_token_type, 1262 | token_text = 1263 | string.sub(tmpl, curr_pos) 1264 | } 1265 | table.insert(tmpl_tokens, token) 1266 | break 1267 | end 1268 | until curr_pos >= string.len(tmpl) 1269 | 1270 | ass_tmpl[osd_n].tokens_media[mediatype_] = tmpl_tokens 1271 | msg.trace("ass_prepare_templates(): tmpl_tokens " .. 1272 | "(" .. osd_n .. "/" .. mediatype_ .. "): " .. 1273 | utils.to_string(tmpl_tokens)) 1274 | end 1275 | end 1276 | end 1277 | 1278 | -- OSD functions 1279 | 1280 | local function show_statusosd() 1281 | if osd_enabled then 1282 | mp.osd_message( 1283 | "Metadata OSD: Enabled " .. 1284 | "(" .. options.key_toggleenable .. "), " .. 1285 | "Autohide: " .. bool2enabled_str(osd_autohide) .. " " .. 1286 | "(" .. options.key_toggleautohide .. ")", 1287 | options.autohide_statusosd_timeout_sec 1288 | ) 1289 | else 1290 | mp.osd_message( 1291 | "Metadata OSD: Disabled " .. 1292 | "(" .. options.key_toggleenable .. ")", 1293 | 1 -- hide abruptly after one second, 1294 | -- exiting ought to be quick 1295 | ) 1296 | end 1297 | end 1298 | 1299 | local function osd_has_data(osd_overlay) 1300 | return str_isnonempty(osd_overlay.data) 1301 | end 1302 | 1303 | local function show_osd_1() 1304 | msg.debug("show_osd_1()") 1305 | 1306 | if osd_enabled then 1307 | if osd_has_data(osd_overlay_osd_1) then 1308 | osd_overlay_osd_2:remove() 1309 | osd_overlay_osd_1:update() 1310 | 1311 | if osd_autohide then 1312 | osd_timer:kill() 1313 | osd_timer:resume() 1314 | end 1315 | 1316 | curr_state = state.SHOWING_OSD_1 1317 | end 1318 | end 1319 | end 1320 | 1321 | local function show_osd_2() 1322 | msg.debug("show_osd_2()") 1323 | 1324 | if osd_enabled then 1325 | if osd_has_data(osd_overlay_osd_2) then 1326 | osd_overlay_osd_1:remove() 1327 | osd_overlay_osd_2:update() 1328 | 1329 | if osd_autohide then 1330 | osd_timer:kill() 1331 | osd_timer:resume() 1332 | end 1333 | 1334 | curr_state = state.SHOWING_OSD_2 1335 | end 1336 | end 1337 | end 1338 | 1339 | local function hide_osd() 1340 | msg.debug("hide_osd()") 1341 | 1342 | if osd_enabled then 1343 | osd_overlay_osd_1:remove() 1344 | osd_overlay_osd_2:remove() 1345 | 1346 | if osd_autohide then 1347 | osd_timer:kill() 1348 | end 1349 | 1350 | curr_state = state.OSD_HIDDEN 1351 | end 1352 | end 1353 | 1354 | local function toggle_osd_1() 1355 | msg.debug("toggle_osd_1()") 1356 | 1357 | if osd_enabled then 1358 | if curr_state == state.SHOWING_OSD_1 then 1359 | hide_osd() 1360 | else 1361 | show_osd_1() 1362 | end 1363 | end 1364 | end 1365 | 1366 | local function toggle_osd_2() 1367 | msg.debug("toggle_osd_2()") 1368 | 1369 | if osd_enabled then 1370 | if curr_state == state.SHOWING_OSD_2 then 1371 | hide_osd() 1372 | else 1373 | show_osd_2() 1374 | end 1375 | end 1376 | end 1377 | 1378 | local function osd_timeout_handler() 1379 | if osd_enabled then 1380 | if osd_autohide then 1381 | if curr_state == state.SHOWING_OSD_1 then 1382 | if osd_has_data(osd_overlay_osd_2) then 1383 | show_osd_2() 1384 | else 1385 | hide_osd() 1386 | end 1387 | elseif curr_state == state.SHOWING_OSD_2 then 1388 | hide_osd() 1389 | end 1390 | end 1391 | end 1392 | end 1393 | 1394 | local function autohide_resettimer() 1395 | if osd_enabled then 1396 | if osd_autohide then 1397 | osd_timer:kill() 1398 | osd_timer:resume() 1399 | else 1400 | osd_timer:kill() 1401 | show_osd_1() 1402 | end 1403 | end 1404 | end 1405 | 1406 | local function toggle_autohide() 1407 | osd_autohide_usertoggled = true 1408 | osd_autohide = not osd_autohide 1409 | autohide_resettimer() 1410 | show_statusosd() 1411 | end 1412 | 1413 | local function reeval_osd_autohide() 1414 | if not osd_autohide_usertoggled then 1415 | osd_autohide = false 1416 | 1417 | if (curr_mediatype == mediatype.AUDIO and options.autohide_for_audio) or 1418 | (curr_mediatype == mediatype.AUDIO_WITHALBUMART and options.autohide_for_audio_withalbumart) or 1419 | (curr_mediatype == mediatype.VIDEO and options.autohide_for_video) or 1420 | (curr_mediatype == mediatype.IMAGE and options.autohide_for_image) 1421 | then 1422 | osd_autohide = true 1423 | end 1424 | 1425 | autohide_resettimer() 1426 | end 1427 | end 1428 | 1429 | local reeval_osd_enabled -- forward declaration 1430 | 1431 | local function reset_usertoggled() 1432 | msg.debug("reset_usertoggled()") 1433 | osd_enabled_usertoggled = false 1434 | osd_autohide_usertoggled = false 1435 | reeval_osd_enabled() 1436 | reeval_osd_autohide() 1437 | show_statusosd() 1438 | end 1439 | 1440 | local function get_abspath() 1441 | local path = 1442 | utils.join_path( 1443 | mp.get_property("working-directory", ""), 1444 | mp.get_property("path", "") 1445 | ) 1446 | 1447 | -- replace all current dir refs '/./' with '/' 1448 | path = string.gsub(path, "([/\\])%.[/\\]", "%1") 1449 | 1450 | return path 1451 | end 1452 | 1453 | local function get_chapter_pos() 1454 | local prop_chapter_curr = 1455 | mp.get_property_number("chapter", -1) + 1 -- zero-indexed 1456 | local prop_chapters_total = 1457 | mp.get_property_number("chapters", -1) 1458 | return prop_chapter_curr, prop_chapters_total 1459 | end 1460 | 1461 | local function mediatype_is_stream() 1462 | local prop_path = mp.get_property_osd("path") 1463 | local prop_streamfilename = mp.get_property_osd("stream-open-filename") 1464 | local prop_fileformat = mp.get_property_osd("file-format") 1465 | 1466 | return 1467 | prop_fileformat == "hls" or -- hls --> http live streaming 1468 | prop_path ~= prop_streamfilename -- path ~= actual media URL 1469 | end 1470 | 1471 | local tmpl_var = { 1472 | playlist_pos = { 1473 | tmpl_key = "PLAYLIST_POS", 1474 | }, 1475 | playlist_count = { 1476 | tmpl_key = "PLAYLIST_COUNT", 1477 | }, 1478 | chapter_curr = { 1479 | tmpl_key = "CHAPTER_CURR", 1480 | }, 1481 | chapter_count = { 1482 | tmpl_key = "CHAPTER_COUNT", 1483 | }, 1484 | chaptertitle = { 1485 | tmpl_key = "CHAPTERTITLE", 1486 | }, 1487 | 1488 | artist = { 1489 | tmpl_key = "ARTIST", 1490 | gatherfunc = function() 1491 | local artist_str = mp.get_property_osd("metadata/by-key/artist") 1492 | if str_isempty(artist_str) 1493 | then 1494 | artist_str = mp.get_property_osd("metadata/by-key/album_artist") 1495 | if str_isempty(artist_str) 1496 | then 1497 | artist_str = mp.get_property_osd("metadata/by-key/composer") 1498 | end 1499 | end 1500 | 1501 | return artist_str 1502 | end 1503 | }, 1504 | 1505 | album = { 1506 | tmpl_key = "ALBUM", 1507 | gatherfunc = function() 1508 | local prop_chapter_curr, prop_chapters_total = 1509 | get_chapter_pos() 1510 | local album_str = 1511 | mp.get_property_osd("metadata/by-key/album") 1512 | 1513 | -- For audio files with internal chapters ... 1514 | if prop_chapter_curr > 0 and 1515 | prop_chapters_total > 0 and 1516 | (curr_mediatype == mediatype.AUDIO or 1517 | curr_mediatype == mediatype.AUDIO_WITHALBUMART) 1518 | then 1519 | if str_isempty(album_str) 1520 | then 1521 | -- meta: Title 1522 | -- contains _often_ album name, use it in a pinch. 1523 | -- sometimes, this contains better data than 'album' property, 1524 | -- but how would we know (switch it on a mouse click?) 1525 | album_str = mp.get_property_osd("metadata/by-key/title") 1526 | end 1527 | end 1528 | 1529 | return album_str 1530 | end 1531 | }, 1532 | 1533 | title = { 1534 | tmpl_key = "TITLE", 1535 | gatherfunc = function() 1536 | local prop_chapter_curr, prop_chapters_total = 1537 | get_chapter_pos() 1538 | local title_str = nil 1539 | 1540 | -- For audio files with internal chapters ... 1541 | if prop_chapter_curr > 0 and 1542 | prop_chapters_total > 0 and 1543 | ( curr_mediatype == mediatype.AUDIO or 1544 | curr_mediatype == mediatype.AUDIO_WITHALBUMART ) 1545 | then 1546 | -- meta: Chapter Title 1547 | -- seems to contain song name for audio files with chapters 1548 | title_str = 1549 | mp.get_property_osd( 1550 | "chapter-list/" .. tostring(prop_chapter_curr - 1) .. "/title") 1551 | 1552 | -- meta: Title 1553 | else 1554 | title_str = mp.get_property_osd("metadata/by-key/title") 1555 | end 1556 | 1557 | return title_str 1558 | end 1559 | }, 1560 | 1561 | release_year = { 1562 | tmpl_key = "RELEASE_YEAR", 1563 | gatherfunc = function() 1564 | local function str_capture4digits(s) 1565 | res = "" 1566 | if str_isnonempty(s) then 1567 | local _, _, s_match = string.find(s, '([%d][%d][%d][%d])') 1568 | if s_match then 1569 | res = s_match 1570 | end 1571 | end 1572 | return res 1573 | end 1574 | 1575 | local prop_chapter_curr, prop_chapters_total = 1576 | get_chapter_pos() 1577 | local release_year_str = 1578 | mp.get_property_osd("metadata/by-key/date") 1579 | release_year_str = 1580 | str_capture4digits(release_year_str) 1581 | 1582 | -- For audio files with internal chapters ... 1583 | if prop_chapter_curr > 0 and 1584 | prop_chapters_total > 0 and 1585 | ( curr_mediatype == mediatype.AUDIO or 1586 | curr_mediatype == mediatype.AUDIO_WITHALBUMART ) 1587 | then 1588 | if str_isempty(release_year_str) 1589 | then 1590 | -- meta: Track 1591 | -- contains _often_ release date, use it in a pinch. 1592 | local prop_meta_track = mp.get_property_osd("metadata/by-key/track") 1593 | prop_meta_track = str_capture4digits(prop_meta_track) 1594 | 1595 | if str_isnonempty(prop_meta_track) 1596 | then 1597 | release_year_str = prop_meta_track 1598 | end 1599 | end 1600 | end 1601 | 1602 | return release_year_str 1603 | end 1604 | }, 1605 | 1606 | albumtrack_num = { 1607 | tmpl_key = "ALBUMTRACK_NUM", 1608 | gatherfunc = function() 1609 | local albumtrack_num_str = nil 1610 | local prop_meta_track = mp.get_property_osd("metadata/by-key/track") 1611 | 1612 | -- meta: Track Number 1613 | if (curr_mediatype == mediatype.AUDIO or 1614 | curr_mediatype == mediatype.AUDIO_WITHALBUMART) and 1615 | str_isnonempty(prop_meta_track) 1616 | then 1617 | local _, _, s_match = string.find(prop_meta_track, '^(%d+)') 1618 | if s_match 1619 | then 1620 | local tracknum = tonumber(s_match) 1621 | if tracknum and 1622 | tracknum < 999 -- track number can contain release year, 1623 | -- skip for more-than-three-digit track numbers 1624 | then 1625 | albumtrack_num_str = tostring(tracknum) 1626 | end 1627 | end 1628 | end 1629 | 1630 | return albumtrack_num_str 1631 | end 1632 | }, 1633 | 1634 | uploader = { 1635 | tmpl_key = "UPLOADER", 1636 | gatherfunc = function() 1637 | local uploader_str = 1638 | mp.get_property_osd("metadata/by-key/uploader") 1639 | return uploader_str 1640 | end 1641 | }, 1642 | 1643 | mediatitle = { 1644 | tmpl_key = "MEDIATITLE", 1645 | gatherfunc = function() 1646 | local mediatitle_str = 1647 | mp.get_property_osd("media-title") 1648 | return mediatitle_str 1649 | end 1650 | }, 1651 | 1652 | dirname_up = { 1653 | tmpl_key = "DIRNAME_UP", 1654 | gatherfunc = function() 1655 | local dirname_up_str = nil 1656 | 1657 | if options.enable_pathname_fallback_dirnameup 1658 | -- FIXME: Remove option below on next release 1659 | and options.enable_pathname_fallback_dirname_up 1660 | and options.enable_pathname_fallback_textarea_1 1661 | then 1662 | dirname_up_str = 1663 | string.match(get_abspath(), ".*[/\\](.*)[/\\].*[/\\].*") 1664 | 1665 | if dirname_up_str 1666 | then 1667 | dirname_up_str = 1668 | pathname_fallback_gsubloop( 1669 | pathname_fallback_type.DIRNAMEUP, dirname_up_str) 1670 | end 1671 | end 1672 | 1673 | return dirname_up_str 1674 | end 1675 | }, 1676 | 1677 | dirname = { 1678 | tmpl_key = "DIRNAME", 1679 | gatherfunc = function() 1680 | local dirname_str = nil 1681 | 1682 | if options.enable_pathname_fallback_dirname 1683 | -- FIXME: Remove option below on next release 1684 | and options.enable_pathname_fallback_textarea_2 1685 | then 1686 | dirname_str = 1687 | string.match(get_abspath(), ".*[/\\](.*)[/\\].*") 1688 | 1689 | if dirname_str 1690 | then 1691 | dirname_str = 1692 | pathname_fallback_gsubloop( 1693 | pathname_fallback_type.DIRNAME, dirname_str) 1694 | end 1695 | end 1696 | 1697 | return dirname_str 1698 | end 1699 | }, 1700 | 1701 | filename = { 1702 | tmpl_key = "FILENAME", 1703 | gatherfunc = function() 1704 | local filename_str = nil 1705 | 1706 | if options.enable_pathname_fallback_filename 1707 | -- FIXME: Remove option below on next release 1708 | and options.enable_pathname_fallback_textarea_3 1709 | then 1710 | filename_str = 1711 | mp.get_property_osd("filename/no-ext") 1712 | 1713 | if filename_str 1714 | then 1715 | filename_str = 1716 | pathname_fallback_gsubloop( 1717 | pathname_fallback_type.FILENAME, filename_str) 1718 | end 1719 | end 1720 | 1721 | return filename_str 1722 | end 1723 | }, 1724 | 1725 | -- FIXME: Remove on next release. Obsolete code. 1726 | textarea_1_gen = { 1727 | tmpl_key = "TEXTAREA_1_GEN", 1728 | }, 1729 | textarea_2_gen = { 1730 | tmpl_key = "TEXTAREA_2_GEN", 1731 | }, 1732 | textarea_3_gen = { 1733 | tmpl_key = "TEXTAREA_3_GEN", 1734 | }, 1735 | textarea_4_gen = { 1736 | tmpl_key = "TEXTAREA_4_GEN", 1737 | }, 1738 | 1739 | textarea_1_metakey = { 1740 | tmpl_key = "TEXTAREA_1_METAKEY", 1741 | }, 1742 | textarea_2_metakey = { 1743 | tmpl_key = "TEXTAREA_2_METAKEY", 1744 | }, 1745 | textarea_3_metakey = { 1746 | tmpl_key = "TEXTAREA_3_METAKEY", 1747 | }, 1748 | textarea_4_metakey = { 1749 | tmpl_key = "TEXTAREA_4_METAKEY", 1750 | }, 1751 | } 1752 | 1753 | local function lazyget_tmpl_data(tmpl_data, tmpl_var_s) 1754 | if not tmpl_data[tmpl_var_s] 1755 | then 1756 | local tmpl_var = tmpl_var[string.lower(tmpl_var_s)] 1757 | 1758 | if tmpl_var and tmpl_var.gatherfunc 1759 | then 1760 | tmpl_data[tmpl_var_s] = tmpl_var.gatherfunc() or "" 1761 | end 1762 | end 1763 | 1764 | return tmpl_data[tmpl_var_s] 1765 | end 1766 | 1767 | local function tmpl_fill_content(tmpl_tokens, tmpl_data) 1768 | local tmpl = "" 1769 | local ORed = false 1770 | 1771 | for _, token in ipairs(tmpl_tokens) 1772 | do 1773 | msg.trace("tmpl_fill_content(): token: " .. 1774 | utils.to_string(token)) 1775 | 1776 | if ORed and token.token_type == tmpl_token_type.COND_OR_END 1777 | then 1778 | ORed = false 1779 | end 1780 | 1781 | if token.token_type == tmpl_token_type.TEXT 1782 | then 1783 | tmpl = tmpl .. token.token_text 1784 | elseif token.token_type == tmpl_token_type.COND_OR 1785 | and not ORed -- keep ORed before lazy eval to event. skip it 1786 | and str_isnonempty(lazyget_tmpl_data(tmpl_data, token.token_var)) 1787 | then 1788 | tmpl = tmpl .. token.token_text 1789 | ORed = true 1790 | end 1791 | end 1792 | 1793 | msg.debug("tmpl_fill_content(): tmpl: " .. tmpl) 1794 | 1795 | local curr_pos = 1 1796 | 1797 | repeat 1798 | local idx_start, 1799 | idx_end, 1800 | tmpl_var = 1801 | string.find(tmpl, "##(.-)##", curr_pos) 1802 | 1803 | if idx_start and idx_end and tmpl_var 1804 | then 1805 | msg.debug("tmpl_fill_content(): found template var: " .. 1806 | tmpl_var) 1807 | -- FIXME: Is it better to build the OSD string here ? 1808 | lazyget_tmpl_data(tmpl_data, tmpl_var) 1809 | curr_pos = idx_end + 1 1810 | else 1811 | break 1812 | end 1813 | until curr_pos >= string.len(tmpl) 1814 | 1815 | for strid, val in pairs(tmpl_data) 1816 | do 1817 | val = tostring(val) 1818 | 1819 | msg.debug("tmpl_fill_content(): " .. 1820 | strid .. " --> " .. val) 1821 | 1822 | tmpl = string.gsub( 1823 | tmpl, 1824 | "##" .. strid .. "##", 1825 | str_isnonempty(val) and 1826 | str_trunc(val) or "", 1827 | 1) 1828 | end 1829 | 1830 | return tmpl 1831 | end 1832 | 1833 | local function on_metadata_change(metadata_key, metadata_val) 1834 | --[[ 1835 | The incoming table with metadata can have all the possible letter 1836 | capitalizations for table keys which are case sensitive in Lua --> 1837 | properties are always querried via mp.get_property_*(). 1838 | ]] 1839 | 1840 | local osd_tmpl = ass_tmpl.osd_1.curr_tmpl 1841 | if not osd_tmpl then 1842 | msg.warn("on_metadata_change(): Template for OSD-1 not yet available.") 1843 | return 1844 | end 1845 | local tmpl_data = {} 1846 | 1847 | if true then -- FIXME: Remove on next release. Obsolete code. 1848 | local playing_file = not mediatype_is_stream() 1849 | 1850 | -- OSD-1 1851 | -- ┌─────────────────┐ 1852 | -- │ TEXT AREA 1 │ 1853 | -- └─────────────────┘ 1854 | local textarea_1_metakey_str = nil 1855 | local textarea_1_str = nil 1856 | 1857 | if playing_file 1858 | then 1859 | if str_isnonempty( 1860 | lazyget_tmpl_data( 1861 | tmpl_data, tmpl_var.artist.tmpl_key)) 1862 | then 1863 | textarea_1_metakey_str = "Artist" 1864 | textarea_1_str = tmpl_data[tmpl_var.artist.tmpl_key] 1865 | 1866 | elseif options.enable_pathname_fallback_textarea_1 1867 | and str_isnonempty(lazyget_tmpl_data( 1868 | tmpl_data, tmpl_var.dirname_up.tmpl_key)) 1869 | then 1870 | textarea_1_metakey_str = "Path" 1871 | textarea_1_str = tmpl_data[tmpl_var.dirname_up.tmpl_key] 1872 | end 1873 | else -- is stream 1874 | if str_isnonempty( 1875 | lazyget_tmpl_data( 1876 | tmpl_data, tmpl_var.uploader.tmpl_key)) 1877 | then 1878 | textarea_1_metakey_str = "Uploader" 1879 | textarea_1_str = tmpl_data[tmpl_var.uploader.tmpl_key] 1880 | end 1881 | end 1882 | 1883 | tmpl_data[tmpl_var.textarea_1_metakey.tmpl_key] = 1884 | textarea_1_metakey_str 1885 | tmpl_data[tmpl_var.textarea_1_gen.tmpl_key] = 1886 | textarea_1_str 1887 | 1888 | -- ┌─────────────────┐ 1889 | -- │ TEXT AREA 2 │ 1890 | -- └─────────────────┘ 1891 | local textarea_2_metakey_str = nil 1892 | local textarea_2_str = nil 1893 | 1894 | if playing_file 1895 | then 1896 | if str_isnonempty( 1897 | lazyget_tmpl_data( 1898 | tmpl_data, tmpl_var.album.tmpl_key)) 1899 | then 1900 | textarea_2_metakey_str = "Album" 1901 | textarea_2_str = tmpl_data[tmpl_var.album.tmpl_key] 1902 | elseif options.enable_pathname_fallback_textarea_2 1903 | and str_isnonempty(lazyget_tmpl_data( 1904 | tmpl_data, tmpl_var.dirname.tmpl_key)) 1905 | then 1906 | textarea_2_metakey_str = "Path" 1907 | textarea_2_str = tmpl_data[tmpl_var.dirname.tmpl_key] 1908 | end 1909 | 1910 | -- else -- is stream 1911 | -- could be filled with something useful in the future. 1912 | end 1913 | 1914 | tmpl_data[tmpl_var.textarea_2_metakey.tmpl_key] = 1915 | textarea_2_metakey_str 1916 | tmpl_data[tmpl_var.textarea_2_gen.tmpl_key] = 1917 | textarea_2_str 1918 | 1919 | -- ┌─────────────────┐ 1920 | -- │ TEXT AREA 3 │ 1921 | -- └─────────────────┘ 1922 | local textarea_3_str = nil 1923 | local textarea_3_metakey_str = nil 1924 | 1925 | if playing_file 1926 | then 1927 | if str_isnonempty( 1928 | lazyget_tmpl_data( 1929 | tmpl_data, tmpl_var.title.tmpl_key)) 1930 | then 1931 | textarea_3_metakey_str = "Title" 1932 | textarea_3_str = tmpl_data[tmpl_var.title.tmpl_key] 1933 | elseif options.enable_pathname_fallback_textarea_3 1934 | and str_isnonempty(lazyget_tmpl_data( 1935 | tmpl_data, tmpl_var.filename.tmpl_key)) 1936 | then 1937 | textarea_3_metakey_str = "File" 1938 | textarea_3_str = tmpl_data[tmpl_var.filename.tmpl_key] 1939 | end 1940 | 1941 | else -- is stream 1942 | if str_isnonempty( 1943 | lazyget_tmpl_data( 1944 | tmpl_data, tmpl_var.mediatitle.tmpl_key)) 1945 | then 1946 | textarea_3_metakey_str = "Media Title" 1947 | textarea_3_str = tmpl_data[tmpl_var.mediatitle.tmpl_key] 1948 | end 1949 | end 1950 | 1951 | tmpl_data[tmpl_var.textarea_3_metakey.tmpl_key] = 1952 | textarea_3_metakey_str 1953 | tmpl_data[tmpl_var.textarea_3_gen.tmpl_key] = 1954 | textarea_3_str 1955 | end 1956 | 1957 | -- ┌─────────────────┐ 1958 | -- │ TEXT AREA 4 │ 1959 | -- └─────────────────┘ 1960 | local prop_playlist_curr = 1961 | mp.get_property_number("playlist-pos-1", 0) 1962 | local prop_playlist_total = 1963 | mp.get_property_number("playlist-count", 0) 1964 | local prop_chapter_curr, prop_chapters_total = 1965 | get_chapter_pos() 1966 | 1967 | local prop_playlist_curr_str = 1968 | tostring(prop_playlist_curr) 1969 | local prop_playlist_total_str = 1970 | tostring(prop_playlist_total) 1971 | local prop_chapter_curr_str = 1972 | tostring(prop_chapter_curr) 1973 | local prop_chapters_total_str = 1974 | tostring(prop_chapters_total) 1975 | 1976 | -- always set tmpl. vars below 1977 | tmpl_data[tmpl_var.playlist_pos.tmpl_key] = 1978 | prop_playlist_curr_str 1979 | tmpl_data[tmpl_var.playlist_count.tmpl_key] = 1980 | prop_playlist_total_str 1981 | tmpl_data[tmpl_var.chapter_curr.tmpl_key] = 1982 | prop_chapter_curr_str 1983 | tmpl_data[tmpl_var.chapter_count.tmpl_key] = 1984 | prop_chapters_total_str 1985 | 1986 | local textarea_4_str = nil 1987 | local textarea_4_metakey_str = nil 1988 | 1989 | -- meta: (Big) Playlist position 1990 | if prop_playlist_curr > 0 and 1991 | prop_playlist_total > 0 1992 | then 1993 | textarea_4_str = 1994 | prop_playlist_curr_str .. 1995 | "/" .. 1996 | prop_playlist_total_str 1997 | 1998 | -- For files with internal chapters ... 1999 | -- meta: Chapter Number 2000 | if prop_chapter_curr > 0 and 2001 | prop_chapters_total > 0 2002 | then 2003 | if options.show_chapternumber 2004 | then 2005 | local chapternum_metakey_str = "Chapter: " 2006 | local chapternum_str = "" 2007 | 2008 | if not ((curr_mediatype == mediatype.AUDIO 2009 | or curr_mediatype == mediatype.AUDIO_WITHALBUMART) 2010 | and prop_playlist_total == 1) 2011 | then 2012 | chapternum_str = chapternum_metakey_str 2013 | end 2014 | 2015 | chapternum_str = 2016 | chapternum_str .. 2017 | prop_chapter_curr_str .. 2018 | "/" .. 2019 | prop_chapters_total_str 2020 | 2021 | if prop_playlist_total ~= 1 and 2022 | (prop_chapter_curr ~= prop_playlist_curr 2023 | or prop_chapters_total ~= prop_playlist_total) 2024 | then 2025 | chapternum_str = 2026 | chapternum_str .. 2027 | ass_newline() .. 2028 | "[" .. 2029 | textarea_4_str .. 2030 | "]" 2031 | end 2032 | 2033 | textarea_4_str = chapternum_str 2034 | end 2035 | 2036 | elseif options.show_albumtracknumber 2037 | and str_isnonempty( 2038 | lazyget_tmpl_data( 2039 | tmpl_data, tmpl_var.albumtrack_num.tmpl_key)) 2040 | then 2041 | local tracknum_str = tmpl_data[tmpl_var.albumtrack_num.tmpl_key] 2042 | local tracknum = tonumber(tracknum_str, 10) 2043 | local tracknum_metakey_str = "Track: " 2044 | local textarea_4_albumtrack_str = "" 2045 | 2046 | if prop_playlist_total == 1 2047 | then 2048 | textarea_4_albumtrack_str = 2049 | tracknum_metakey_str .. 2050 | tracknum_str 2051 | 2052 | elseif tracknum ~= prop_playlist_curr 2053 | then 2054 | textarea_4_albumtrack_str = 2055 | tracknum_metakey_str .. 2056 | tracknum_str .. 2057 | ass_newline() .. 2058 | "[" .. 2059 | textarea_4_str .. 2060 | "]" 2061 | end 2062 | 2063 | if str_isnonempty(textarea_4_albumtrack_str) 2064 | then 2065 | textarea_4_str = textarea_4_albumtrack_str 2066 | end 2067 | end 2068 | end 2069 | 2070 | tmpl_data[tmpl_var.textarea_4_gen.tmpl_key] = 2071 | textarea_4_str 2072 | 2073 | osd_overlay_osd_1.data = 2074 | tmpl_fill_content(osd_tmpl, tmpl_data) 2075 | 2076 | -- OSD-2 2077 | -- ┌─────────────────┐ 2078 | -- │ TEXT AREA 1 │ 2079 | -- └─────────────────┘ 2080 | osd_tmpl = ass_tmpl.osd_2.curr_tmpl 2081 | 2082 | if osd_tmpl 2083 | then 2084 | -- meta: Chapter Title 2085 | if options.enable_osd_2 2086 | and metadata_key == "chapter-metadata/title" 2087 | and str_isnonempty(metadata_val) 2088 | then 2089 | tmpl_data = {} 2090 | tmpl_data[tmpl_var.chaptertitle.tmpl_key] = 2091 | metadata_val 2092 | osd_overlay_osd_2.data = 2093 | tmpl_fill_content(osd_tmpl, tmpl_data) 2094 | end 2095 | else 2096 | msg.warn("on_metadata_change(): Template for OSD-2 not yet available.") 2097 | end 2098 | 2099 | if metadata_key == "chapter-metadata/title" and (curr_state == state.SHOWING_OSD_2 or (osd_autohide and curr_state == state.OSD_HIDDEN)) then 2100 | show_osd_2() 2101 | else 2102 | show_osd_1() 2103 | end 2104 | end 2105 | 2106 | local function master_osd_enable() 2107 | msg.debug("master_osd_enable()") 2108 | 2109 | mp.add_key_binding( 2110 | options.key_toggleosd_1, 2111 | "toggleosd_1", 2112 | toggle_osd_1) 2113 | 2114 | mp.add_key_binding( 2115 | options.key_toggleosd_2, 2116 | "toggleosd_2", 2117 | toggle_osd_2) 2118 | 2119 | mp.add_key_binding( 2120 | options.key_toggleautohide, 2121 | "toggleautohide", 2122 | toggle_autohide) 2123 | 2124 | mp.add_key_binding( 2125 | options.key_reset_usertoggled, 2126 | "reset_usertoggled", 2127 | reset_usertoggled) 2128 | 2129 | mp.observe_property( 2130 | "metadata", 2131 | "native", 2132 | on_metadata_change) 2133 | 2134 | mp.observe_property( 2135 | "chapter-metadata/title", 2136 | "string", 2137 | on_metadata_change) 2138 | 2139 | osd_enabled = true 2140 | show_osd_1() 2141 | end 2142 | 2143 | local function master_osd_disable() 2144 | msg.debug("master_osd_disable()") 2145 | 2146 | mp.remove_key_binding( 2147 | "toggleautohide") 2148 | mp.remove_key_binding( 2149 | "toggleosd_1") 2150 | mp.remove_key_binding( 2151 | "toggleosd_2") 2152 | mp.remove_key_binding( 2153 | "reset_usertoggled") 2154 | 2155 | mp.unobserve_property( 2156 | on_metadata_change) 2157 | 2158 | hide_osd() 2159 | osd_enabled = false 2160 | end 2161 | 2162 | local function toggle_enable() 2163 | osd_enabled_usertoggled = true 2164 | 2165 | if osd_enabled then 2166 | master_osd_disable() 2167 | else 2168 | master_osd_enable() 2169 | reeval_osd_autohide() 2170 | end 2171 | 2172 | show_statusosd() 2173 | end 2174 | 2175 | reeval_osd_enabled = function() 2176 | if not osd_enabled_usertoggled then 2177 | osd_enabled_currstate = osd_enabled 2178 | osd_enabled_newstate = false 2179 | 2180 | if options.enable_on_start then 2181 | if (curr_mediatype == mediatype.AUDIO and options.enable_for_audio) or 2182 | (curr_mediatype == mediatype.AUDIO_WITHALBUMART and options.enable_for_audio_withalbumart) or 2183 | (curr_mediatype == mediatype.VIDEO and options.enable_for_video) or 2184 | (curr_mediatype == mediatype.IMAGE and options.enable_for_image) 2185 | then 2186 | osd_enabled_newstate = true 2187 | end 2188 | end 2189 | 2190 | if osd_enabled_currstate and not osd_enabled_newstate then 2191 | master_osd_disable() 2192 | elseif not osd_enabled_currstate and osd_enabled_newstate then 2193 | master_osd_enable() 2194 | end 2195 | end 2196 | end 2197 | 2198 | local function on_start_file() 2199 | osd_overlay_osd_1.data = nil 2200 | osd_overlay_osd_2.data = nil 2201 | end 2202 | 2203 | local function on_tracklist_change(name, tracklist) 2204 | msg.debug("on_tracklist_change()") 2205 | 2206 | local prev_mediatype = curr_mediatype 2207 | local prev_mediatype_is_stream = curr_mediatype == mediatype.STREAM 2208 | curr_mediatype = nil 2209 | 2210 | if tracklist then 2211 | msg.debug("on_tracklist_change(): num of tracks: " .. tostring(#tracklist)) 2212 | 2213 | for _, track in ipairs(tracklist) do 2214 | if track.selected then 2215 | if track.type == "audio" and not curr_mediatype then 2216 | msg.debug("on_tracklist_change(): audio track selected") 2217 | curr_mediatype = mediatype.AUDIO 2218 | elseif track.type == "video" then 2219 | msg.debug("on_tracklist_change(): video track selected") 2220 | curr_mediatype = mediatype.VIDEO 2221 | if track.image then 2222 | msg.debug("on_tracklist_change(): video track is image") 2223 | curr_mediatype = mediatype.IMAGE 2224 | if track.albumart then 2225 | msg.debug("on_tracklist_change(): video track is albumart.") 2226 | curr_mediatype = mediatype.AUDIO_WITHALBUMART 2227 | end 2228 | end 2229 | end 2230 | end 2231 | end 2232 | end 2233 | 2234 | local curr_mediatype_is_stream = mediatype_is_stream() 2235 | 2236 | if curr_mediatype 2237 | and (prev_mediatype ~= curr_mediatype 2238 | or prev_mediatype_is_stream ~= curr_mediatype_is_stream) 2239 | then 2240 | msg.debug("on_tracklist_change(): current media type: " .. 2241 | curr_mediatype:gsub("^%l", string.upper) .. 2242 | (curr_mediatype_is_stream and " (stream)" or "")) 2243 | 2244 | ass_tmpl.osd_1.curr_tmpl = nil 2245 | ass_tmpl.osd_2.curr_tmpl = nil 2246 | 2247 | if curr_mediatype_is_stream 2248 | then 2249 | ass_tmpl.osd_1.curr_tmpl = 2250 | ass_tmpl.osd_1.tokens_media[mediatype.STREAM] 2251 | ass_tmpl.osd_2.curr_tmpl = 2252 | ass_tmpl.osd_2.tokens_media[mediatype.STREAM] 2253 | else 2254 | for _, mediatype_ in pairs(mediatype) 2255 | do 2256 | if curr_mediatype == mediatype_ 2257 | then 2258 | ass_tmpl.osd_1.curr_tmpl = 2259 | ass_tmpl.osd_1.tokens_media[mediatype_] 2260 | ass_tmpl.osd_2.curr_tmpl = 2261 | ass_tmpl.osd_2.tokens_media[mediatype_] 2262 | break 2263 | end 2264 | end 2265 | end 2266 | end 2267 | 2268 | reeval_osd_enabled() 2269 | reeval_osd_autohide() 2270 | end 2271 | 2272 | ass_prepare_templates() 2273 | prepare_pathname_fallback_gsubtable() 2274 | 2275 | mp.add_key_binding( 2276 | options.key_toggleenable, 2277 | "toggleenable", 2278 | toggle_enable) 2279 | 2280 | mp.add_key_binding( 2281 | options.key_showstatusosd, 2282 | "showstatusosd", 2283 | show_statusosd) 2284 | 2285 | mp.register_event( 2286 | "start-file", 2287 | on_start_file) 2288 | 2289 | mp.observe_property( 2290 | "track-list", 2291 | "native", 2292 | on_tracklist_change) 2293 | 2294 | -- FIXME: Create but don't start the timer. How? 2295 | osd_timer = mp.add_timeout( -- create & start the timer 2296 | options.autohide_timeout_sec, 2297 | osd_timeout_handler 2298 | ) 2299 | osd_timer:kill() -- stop & reset the timer 2300 | -------------------------------------------------------------------------------- /sshots/sshot_albumtracknumber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vc-01/metadata-osd/c8581ea1786c8fce1cfec570c17dd07f8fafc9ea/sshots/sshot_albumtracknumber.png -------------------------------------------------------------------------------- /sshots/sshot_chapternumber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vc-01/metadata-osd/c8581ea1786c8fce1cfec570c17dd07f8fafc9ea/sshots/sshot_chapternumber.png -------------------------------------------------------------------------------- /sshots/sshot_osd_1_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vc-01/metadata-osd/c8581ea1786c8fce1cfec570c17dd07f8fafc9ea/sshots/sshot_osd_1_dark.png -------------------------------------------------------------------------------- /sshots/sshot_osd_1_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vc-01/metadata-osd/c8581ea1786c8fce1cfec570c17dd07f8fafc9ea/sshots/sshot_osd_1_light.png --------------------------------------------------------------------------------