diff --git a/.github/codeql-config.yml b/.github/codeql-config.yml new file mode 100644 index 00000000..574f9b33 --- /dev/null +++ b/.github/codeql-config.yml @@ -0,0 +1,4 @@ +name: CodeQL Config + +paths-ignore: + - lib diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..e914485d --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,38 @@ +name: CodeQL + +on: + push: + branches: [nightly] + pull_request: + branches: [nightly] + schedule: + - cron: '05 10 * * 1' + +jobs: + codeql-analysis: + name: CodeQL Analysis + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ['javascript', 'python'] + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + config-file: ./.github/codeql-config.yml + languages: ${{ matrix.language }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 125cae51..6480575f 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -13,7 +13,7 @@ jobs: if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} steps: - name: Checkout Code - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3 - name: Prepare id: prepare @@ -47,7 +47,7 @@ jobs: version: latest - name: Cache Docker Layers - uses: actions/cache@v3.2.0 + uses: actions/cache@v3 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} @@ -70,7 +70,7 @@ jobs: password: ${{ secrets.GHCR_TOKEN }} - name: Docker Build and Push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 if: success() with: context: . diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml index 3b271a1b..49d53233 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3 - name: Set Release Version id: get_version @@ -52,7 +52,7 @@ jobs: echo $GITHUB_SHA > version.txt - name: Set Up Python - uses: actions/setup-python@v4.4.0 + uses: actions/setup-python@v4 with: python-version: '3.9' cache: pip @@ -119,7 +119,10 @@ jobs: run: | CHANGELOG="$( sed -n '/^## /{p; :loop n; p; /^## /q; b loop}' CHANGELOG.md \ | sed '$d' | sed '$d' | sed '$d' )" - echo "CHANGELOG=${CHANGELOG}" >> $GITHUB_OUTPUT + EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) + echo "CHANGELOG<<$EOF" >> $GITHUB_OUTPUT + echo "$CHANGELOG" >> $GITHUB_OUTPUT + echo "$EOF" >> $GITHUB_OUTPUT - name: Create Release uses: actions/create-release@v1 diff --git a/.github/workflows/publish-snap.yml b/.github/workflows/publish-snap.yml index 7ad8fe95..9df4d2fd 100644 --- a/.github/workflows/publish-snap.yml +++ b/.github/workflows/publish-snap.yml @@ -20,7 +20,7 @@ jobs: - armhf steps: - name: Checkout Code - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3 - name: Prepare id: prepare diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index d7c8e45d..58cb4ee4 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v3.2.0 + uses: actions/checkout@v3 - name: Comment on Pull Request uses: mshick/add-pr-comment@v2 diff --git a/.github/workflows/submit-winget.yml b/.github/workflows/submit-winget.yml index 43c1ce24..aa1c4dec 100644 --- a/.github/workflows/submit-winget.yml +++ b/.github/workflows/submit-winget.yml @@ -9,6 +9,7 @@ jobs: winget: name: Submit Winget Package runs-on: windows-latest + if: ${{ !github.event.release.prerelease }} steps: - name: Submit package to Windows Package Manager Community Repository run: | diff --git a/.gitignore b/.gitignore index 68d5abaf..1e54132b 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,9 @@ Thumbs.db #Ignore files generated by PyCharm *.idea/* +#Ignore files generated by VSCode +*.vscode/* + #Ignore files generated by vi *.swp diff --git a/CHANGELOG.md b/CHANGELOG.md index 79b3546a..24baf072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,78 @@ # Changelog +## v2.12.4 (2023-05-23) + +* History: + * Fix: Set view offset equal to duration if a stream is stopped within the last 10 sec. +* Other: + * Fix: Database import may fail for some older databases. + * Fix: Double-quoted strings for newer versions of SQLite. (#2015, #2057) +* API: + * Change: Return the ID for async API calls (export_metadata, notify, notify_newsletter). + + +## v2.12.3 (2023-04-14) + +* Activity: + * Fix: Incorrect subtitle decision shown when subtitles are transcoded. +* History: + * Fix: Incorrect order when sorting by the duration column in the history tables. +* Notifications: + * Fix: Logging error when running scripts that use PlexAPI. +* UI: + * Fix: Calculate file sizes setting causing the media info table to fail to load. + * Fix: Incorrect artwork and thumbnail shown for Live TV on the Most Active Libraries statistics card. +* API: + * Change: Renamed duration to play_duration in the get_history API response. (Note: duration kept for backwards compatibility.) + + +## v2.12.2 (2023-03-16) + +* Other: + * Fix: Tautulli not starting on FreeBSD jails. + + +## v2.12.1 (2023-03-14) + +* Activity: + * Fix: Stop checking for deprecated sync items sessions. + * Change: Do not show audio language on activity cards for music. +* Other: + * Fix: Tautulli not starting on macOS. + + +## v2.12.0 (2023-03-13) + +* Notifications: + * New: Added support for Telegram group topics. (#1980) + * New: Added anidb_id and anidb_url notification parameters. (#1973) + * New: Added notification triggers for Intro Marker, Commercial Marker, and Credits Marker. + * New: Added various intro, commercial, and credits marker notification parameters. + * New: Allow setting a custom Pushover notification sound. (#2005) + * Change: Notification images are now uploaded directly to Discord without the need for a 3rd party image hosting service. + * Change: Automatically strip whitespace from notification condition values. + * Change: Trigger watched notifications based on the video watched completion behaviour setting. +* Exporter: + * Fix: Unable to run exporter when using the Snap package. (#2007) + * New: Added credits marker, and audio/subtitle settings to export fields. +* UI: + * Fix: Incorrect styling and missing content for collection media info pages. + * New: Added edition details field on movie media info pages. (#1957) (Thanks @herby2212) + * New: Added setting to change the video watched completion behaviour. + * New: Added watch time and user statistics to collection and playlist media info pages. (#1982, #2012) (Thanks @herby2212) + * New: Added history table to collection and playlist media info pages. + * New: Dynamically change watched status in the UI based on video watched completion behaviour setting. + * New: Added hidden setting to override server name. + * Change: Move track artist to a details field instead of in the title on track media info pages. +* API: + * New: Added section_id and user_id parameters to get_home_stats API command. (#1944) + * New: Added marker info to get_metadata API command results. + * New: Added media_type parameter to get_item_watch_time_stats and get_item_user_stats API commands. (#1982) (Thanks @herby2212) + * New: Added last_refreshed timestamp to get_library_media_info API command response. +* Other: + * Change: Migrate analytics to Google Analytics 4. + + ## v2.11.1 (2022-12-22) * Activity: diff --git a/README.md b/README.md index fa35aab8..d38f69e1 100644 --- a/README.md +++ b/README.md @@ -57,24 +57,24 @@ Read the [Installation Guides][Installation] for instructions on how to install [badge-release-nightly-last-commit]: https://img.shields.io/github/last-commit/Tautulli/Tautulli/nightly?style=flat-square&color=blue [badge-release-nightly-commits]: https://img.shields.io/github/commits-since/Tautulli/Tautulli/latest/nightly?style=flat-square&color=blue [badge-docker-master]: https://img.shields.io/badge/docker-latest-blue?style=flat-square -[badge-docker-master-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker/master?style=flat-square +[badge-docker-master-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-docker.yml?style=flat-square&branch=master [badge-docker-beta]: https://img.shields.io/badge/docker-beta-blue?style=flat-square -[badge-docker-beta-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker/beta?style=flat-square +[badge-docker-beta-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-docker.yml?style=flat-square&branch=beta [badge-docker-nightly]: https://img.shields.io/badge/docker-nightly-blue?style=flat-square -[badge-docker-nightly-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker/nightly?style=flat-square +[badge-docker-nightly-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-docker.yml?style=flat-square&branch=nightly [badge-snap-master]: https://img.shields.io/badge/snap-stable-blue?style=flat-square -[badge-snap-master-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Snap/master?style=flat-square +[badge-snap-master-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-snap.yml?style=flat-square&branch=master [badge-snap-beta]: https://img.shields.io/badge/snap-beta-blue?style=flat-square -[badge-snap-beta-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Snap/beta?style=flat-square +[badge-snap-beta-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-snap.yml?style=flat-square&branch=beta [badge-snap-nightly]: https://img.shields.io/badge/snap-edge-blue?style=flat-square -[badge-snap-nightly-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Snap/nightly?style=flat-square +[badge-snap-nightly-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-snap.yml?style=flat-square&branch=nightly [badge-installer-master-win]: https://img.shields.io/github/v/release/Tautulli/Tautulli?label=windows&style=flat-square [badge-installer-master-macos]: https://img.shields.io/github/v/release/Tautulli/Tautulli?label=macos&style=flat-square -[badge-installer-master-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Installers/master?style=flat-square +[badge-installer-master-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-installers.yml?style=flat-square&branch=master [badge-installer-beta-win]: https://img.shields.io/github/v/release/Tautulli/Tautulli?label=windows&include_prereleases&style=flat-square [badge-installer-beta-macos]: https://img.shields.io/github/v/release/Tautulli/Tautulli?label=macos&include_prereleases&style=flat-square -[badge-installer-beta-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Installers/beta?style=flat-square -[badge-installer-nightly-ci]: https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Installers/nightly?style=flat-square +[badge-installer-beta-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-installers.yml?style=flat-square&branch=beta +[badge-installer-nightly-ci]: https://img.shields.io/github/actions/workflow/status/Tautulli/Tautulli/.github/workflows/publish-installers.yml?style=flat-square&branch=nightly ## Support diff --git a/data/interfaces/default/css/bootstrap-select.min.css b/data/interfaces/default/css/bootstrap-select.min.css new file mode 100644 index 00000000..d22faa63 --- /dev/null +++ b/data/interfaces/default/css/bootstrap-select.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap-select v1.13.14 (https://developer.snapappointments.com/bootstrap-select) + * + * Copyright 2012-2020 SnapAppointments, LLC + * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) + */@-webkit-keyframes bs-notify-fadeOut{0%{opacity:.9}100%{opacity:0}}@-o-keyframes bs-notify-fadeOut{0%{opacity:.9}100%{opacity:0}}@keyframes bs-notify-fadeOut{0%{opacity:.9}100%{opacity:0}}.bootstrap-select>select.bs-select-hidden,select.bs-select-hidden,select.selectpicker{display:none!important}.bootstrap-select{width:220px\0;vertical-align:middle}.bootstrap-select>.dropdown-toggle{position:relative;width:100%;text-align:right;white-space:nowrap;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.bootstrap-select>.dropdown-toggle:after{margin-top:-1px}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:#999}.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:hover{color:rgba(255,255,255,.5)}.bootstrap-select>select{position:absolute!important;bottom:0;left:50%;display:block!important;width:.5px!important;height:100%!important;padding:0!important;opacity:0!important;border:none;z-index:0!important}.bootstrap-select>select.mobile-device{top:0;left:0;display:block!important;width:100%!important;z-index:2!important}.bootstrap-select.is-invalid .dropdown-toggle,.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle,.was-validated .bootstrap-select select:invalid+.dropdown-toggle{border-color:#b94a48}.bootstrap-select.is-valid .dropdown-toggle,.was-validated .bootstrap-select select:valid+.dropdown-toggle{border-color:#28a745}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus,.bootstrap-select>select.mobile-device:focus+.dropdown-toggle{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none;height:auto}:not(.input-group)>.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{float:none;z-index:auto}.form-inline .bootstrap-select,.form-inline .bootstrap-select.form-control:not([class*=col-]){width:auto}.bootstrap-select:not(.input-group-btn),.bootstrap-select[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.dropdown-menu-right,.bootstrap-select[class*=col-].dropdown-menu-right,.row .bootstrap-select[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select,.form-horizontal .bootstrap-select,.form-inline .bootstrap-select{margin-bottom:0}.form-group-lg .bootstrap-select.form-control,.form-group-sm .bootstrap-select.form-control{padding:0}.form-group-lg .bootstrap-select.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-lg .dropdown-toggle,.bootstrap-select.form-control-sm .dropdown-toggle{font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-sm .dropdown-toggle{padding:.25rem .5rem}.bootstrap-select.form-control-lg .dropdown-toggle{padding:.5rem 1rem}.form-inline .bootstrap-select .form-control{width:100%}.bootstrap-select.disabled,.bootstrap-select>.disabled{cursor:not-allowed}.bootstrap-select.disabled:focus,.bootstrap-select>.disabled:focus{outline:0!important}.bootstrap-select.bs-container{position:absolute;top:0;left:0;height:0!important;padding:0!important}.bootstrap-select.bs-container .dropdown-menu{z-index:1060}.bootstrap-select .dropdown-toggle .filter-option{position:static;top:0;left:0;float:left;height:100%;width:100%;text-align:left;overflow:hidden;-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto}.bs3.bootstrap-select .dropdown-toggle .filter-option{padding-right:inherit}.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option{position:absolute;padding-top:inherit;padding-bottom:inherit;padding-left:inherit;float:none}.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner{padding-right:inherit}.bootstrap-select .dropdown-toggle .filter-option-inner-inner{overflow:hidden}.bootstrap-select .dropdown-toggle .filter-expand{width:0!important;float:left;opacity:0!important;overflow:hidden}.bootstrap-select .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.input-group .bootstrap-select.form-control .dropdown-toggle{border-radius:inherit}.bootstrap-select[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu>.inner:focus{outline:0!important}.bootstrap-select .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select .dropdown-menu li{position:relative}.bootstrap-select .dropdown-menu li.active small{color:rgba(255,255,255,.5)!important}.bootstrap-select .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select .dropdown-menu li a span.check-mark{display:none}.bootstrap-select .dropdown-menu li a span.text{display:inline-block}.bootstrap-select .dropdown-menu li small{padding-left:.5em}.bootstrap-select .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu .notify.fadeOut{-webkit-animation:.3s linear 750ms forwards bs-notify-fadeOut;-o-animation:.3s linear 750ms forwards bs-notify-fadeOut;animation:.3s linear 750ms forwards bs-notify-fadeOut}.bootstrap-select .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.fit-width .dropdown-toggle .filter-option{position:static;display:inline;padding:0}.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner{display:inline}.bootstrap-select.fit-width .dropdown-toggle .bs-caret:before{content:'\00a0'}.bootstrap-select.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark{position:absolute;display:inline-block;right:15px;top:5px}.bootstrap-select.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select .bs-ok-default:after{content:'';display:block;width:.5em;height:1em;border-style:solid;border-width:0 .26em .26em 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before{bottom:auto;top:-4px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after{bottom:auto;top:-4px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:before,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none} \ No newline at end of file diff --git a/data/interfaces/default/css/tautulli.css b/data/interfaces/default/css/tautulli.css index ac99ae76..e256d2d7 100644 --- a/data/interfaces/default/css/tautulli.css +++ b/data/interfaces/default/css/tautulli.css @@ -79,7 +79,6 @@ select.form-control { color: #eee !important; border: 0px solid #444 !important; background: #555 !important; - padding: 1px 2px; transition: background-color .3s; } .selectize-control.form-control .selectize-input { @@ -87,7 +86,6 @@ select.form-control { align-items: center; flex-wrap: wrap; margin-bottom: 4px; - padding-left: 5px; } .selectize-control.form-control.selectize-pms-ip .selectize-input { padding-left: 12px !important; @@ -2916,7 +2914,7 @@ a .home-platforms-list-cover-face:hover margin-bottom: -20px; width: 100%; max-width: 1750px; - overflow: hidden; + display: flow-root; } .table-card-back td { font-size: 12px; diff --git a/data/interfaces/default/current_activity_instance.html b/data/interfaces/default/current_activity_instance.html index b48a70f3..64b498e0 100644 --- a/data/interfaces/default/current_activity_instance.html +++ b/data/interfaces/default/current_activity_instance.html @@ -265,12 +265,15 @@ DOCUMENTATION :: END
Audio
% if data['stream_audio_decision']: + <% + audio_language = (data['audio_language'] or 'Unknown') + ' - ' if data['media_type'] != 'track' else '' + %> % if data['stream_audio_decision'] == 'transcode': - Transcode (${data['audio_language'] or 'Unknown'} - ${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()} ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + Transcode (${audio_language}${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()} ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) % elif data['stream_audio_decision'] == 'copy': - Direct Stream (${data['audio_language'] or 'Unknown'} - ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + Direct Stream (${audio_language}${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) % else: - Direct Play (${data['audio_language'] or 'Unknown'} - ${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) + Direct Play (${audio_language}${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()}) % endif % endif
diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index b74820c8..7cbe27dc 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -1,6 +1,7 @@ <%inherit file="base.html"/> <%def name="headIncludes()"> + @@ -14,9 +15,7 @@
@@ -239,6 +238,7 @@ <%def name="javascriptIncludes()"> + @@ -379,8 +379,8 @@ //$(current_tab).addClass('active'); - $('.days').html(current_day_range); - $('.months').html(current_month_range); + $('.days').text(current_day_range); + $('.months').text(current_month_range); // Load user ids and names (for the selector) $.ajax({ @@ -388,14 +388,35 @@ type: 'get', dataType: "json", success: function (data) { - var select = $('#graph-user'); + let select = $('#graph-user'); + let by_id = {}; data.sort(function(a, b) { return a.friendly_name.localeCompare(b.friendly_name); }); data.forEach(function(item) { select.append(''); + by_id[item.user_id] = item.friendly_name; }); + select.selectpicker({ + countSelectedText: function(sel, total) { + if (sel === 0 || sel === total) { + return 'All users'; + } else if (sel > 1) { + return sel + ' users'; + } else { + return select.val().map(function(id) { + return by_id[id]; + }).join(', '); + } + }, + style: 'btn-dark', + actionsBox: true, + selectedTextFormat: 'count', + noneSelectedText: 'All users' + }); + select.selectpicker('render'); + select.selectpicker('selectAll'); } }); @@ -644,11 +665,6 @@ $('#nav-tabs-total').tab('show'); } - // Set initial state - if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); } - if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); } - if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); } - // Tab1 opened $('#nav-tabs-plays').on('shown.bs.tab', function (e) { e.preventDefault(); @@ -681,7 +697,7 @@ setLocalStorage('graph_days', current_day_range); if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); } if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); } - $('.days').html(current_day_range); + $('.days').text(current_day_range); }); // Month range changed @@ -691,12 +707,23 @@ current_month_range = $(this).val(); setLocalStorage('graph_months', current_month_range); if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); } - $('.months').html(current_month_range); + $('.months').text(current_month_range); }); + let graph_user_last_id = undefined; + // User changed $('#graph-user').on('change', function() { - selected_user_id = $(this).val() || null; + let val = $(this).val(); + if (val.length === 0 || val.length === $(this).children().length) { + selected_user_id = null; // if all users are selected, just send an empty list + } else { + selected_user_id = val.join(","); + } + if (selected_user_id === graph_user_last_id) { + return; + } + graph_user_last_id = selected_user_id; if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); } if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); } if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); } diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index cf15c87f..8ab8b19e 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -1,6 +1,7 @@ <%inherit file="base.html"/> <%def name="headIncludes()"> + @@ -31,9 +32,7 @@ % if _session['user_group'] == 'admin':
@@ -84,7 +83,7 @@ Started Paused Stopped - Duration + Duration @@ -121,6 +120,7 @@ <%def name="javascriptIncludes()"> + @@ -134,17 +134,40 @@ type: 'GET', dataType: 'json', success: function (data) { - var select = $('#history-user'); + let select = $('#history-user'); + let by_id = {}; data.sort(function (a, b) { return a.friendly_name.localeCompare(b.friendly_name); }); data.forEach(function (item) { select.append(''); + by_id[item.user_id] = item.friendly_name; }); + select.selectpicker({ + countSelectedText: function(sel, total) { + if (sel === 0 || sel === total) { + return 'All users'; + } else if (sel > 1) { + return sel + ' users'; + } else { + return select.val().map(function(id) { + return by_id[id]; + }).join(', '); + } + }, + style: 'btn-dark', + actionsBox: true, + selectedTextFormat: 'count', + noneSelectedText: 'All users' + }); + select.selectpicker('render'); + select.selectpicker('selectAll'); } }); + let history_user_last_id = undefined; + function loadHistoryTable(media_type, transcode_decision, selected_user_id) { history_table_options.ajax = { url: 'get_history', @@ -187,7 +210,16 @@ }); $('#history-user').on('change', function () { - selected_user_id = $(this).val() || null; + let val = $(this).val(); + if (val.length === 0 || val.length === $(this).children().length) { + selected_user_id = null; // if all users are selected, just send an empty list + } else { + selected_user_id = val.join(","); + } + if (selected_user_id === history_user_last_id) { + return; + } + history_user_last_id = selected_user_id; history_table.draw(); }); } diff --git a/data/interfaces/default/history_table_modal.html b/data/interfaces/default/history_table_modal.html index 8ed42bf5..b4066627 100644 --- a/data/interfaces/default/history_table_modal.html +++ b/data/interfaces/default/history_table_modal.html @@ -32,7 +32,7 @@ Started Paused Stopped - Duration + Duration diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html index df2b7b69..d66dd1d4 100644 --- a/data/interfaces/default/home_stats.html +++ b/data/interfaces/default/home_stats.html @@ -77,7 +77,8 @@ DOCUMENTATION :: END <% fallback = 'art-live' if row0['live'] else 'art' %>
% elif stat_id == 'top_libraries': -
+ <% fallback = 'art-live' if row0['live'] else row0['library_art'] %> +
% elif stat_id == 'top_users':
% elif stat_id == 'top_platforms': @@ -109,8 +110,8 @@ DOCUMENTATION :: END
% elif stat_id == 'top_libraries': - % if row0['thumb'].startswith('http'): - + % if row0['library_thumb'].startswith('http'): + % else: % endif @@ -147,7 +148,8 @@ DOCUMENTATION :: END data-rating_key="${row.get('rating_key')}" data-grandparent_rating_key="${row.get('grandparent_rating_key')}" data-guid="${row.get('guid')}" data-title="${row.get('title')}" data-art="${row.get('art')}" data-thumb="${row.get('thumb')}" data-platform="${row.get('platform_name')}" data-library-type="${row.get('section_type')}" data-user_id="${row.get('user_id')}" data-user="${row.get('user')}" data-friendly_name="${row.get('friendly_name')}" data-user_thumb="${row.get('user_thumb')}" - data-last_watch="${row.get('last_watch')}" data-started="${row.get('started')}" data-live="${row.get('live')}" data-library_art="${row.get('library_art', '')}"> + data-last_watch="${row.get('last_watch')}" data-started="${row.get('started')}" data-live="${row.get('live')}" + data-library_art="${row.get('library_art', '')}" data-library_thumb="${row.get('library_thumb', '')}">
${loop.index + 1}
% if stat_id in ('top_movies', 'popular_movies', 'top_tv', 'popular_tv', 'top_music', 'popular_music', 'last_watched'): diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index e094d0e4..57236e69 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -523,14 +523,15 @@ var audio_decision = ''; if (['movie', 'episode', 'clip', 'track'].indexOf(s.media_type) > -1 && s.stream_audio_decision) { + var audio_language = (s.media_type !== 'track') ? (s.audio_language || 'Unknown') + ' - ' : ''; var a_codec = (s.audio_codec === 'truehd') ? 'TrueHD' : s.audio_codec.toUpperCase(); var sa_codec = (s.stream_audio_codec === 'truehd') ? 'TrueHD' : s.stream_audio_codec.toUpperCase(); if (s.stream_audio_decision === 'transcode') { - audio_decision = 'Transcode ('+ (s.audio_language || 'Unknown')+ ' - ' + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ' ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; + audio_decision = 'Transcode (' + audio_language + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ' ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; } else if (s.stream_audio_decision === 'copy') { - audio_decision = 'Direct Stream ('+ (s.audio_language || 'Unknown')+ ' - ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; + audio_decision = 'Direct Stream (' + audio_language + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; } else { - audio_decision = 'Direct Play ('+ (s.audio_language || 'Unknown')+ ' - ' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; + audio_decision = 'Direct Play (' + audio_language + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')'; } } $('#audio_decision-' + key).html(audio_decision); @@ -797,6 +798,7 @@ var guid = $(elem).data('guid'); var live = $(elem).data('live'); var library_art = $(elem).data('library_art'); + var library_thumb = $(elem).data('library_thumb'); var [height, fallback_poster, fallback_art] = [450, 'poster', 'art']; if ($.inArray(stat_id, ['top_music', 'popular_music']) > -1) { [height, fallback_poster, fallback_art] = [300, 'cover', 'art']; @@ -808,11 +810,11 @@ if (stat_id === 'most_concurrent') { return } else if (stat_id === 'top_libraries') { - $('#stats-background-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', art || library_art, null, 500, 280, 40, '282828', 3, library_art || fallback_art) + ')'); + $('#stats-background-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', art || library_art, null, 500, 280, 40, '282828', 3, fallback_art) + ')'); $('#stats-thumb-' + stat_id).removeClass(function (index, className) { return (className.match (/(^|\s)svg-icon library-\S+/g) || []).join(' ')}); - if (thumb.startsWith('http')) { - $('#stats-thumb-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', thumb, null, 300, 300, null, null, null, 'cover') + ')'); + if (library_thumb.startsWith('http')) { + $('#stats-thumb-' + stat_id).css('background-image', 'url(' + page('pms_image_proxy', library_thumb, null, 100, 100, null, null, null, 'cover') + ')'); } else { $('#stats-thumb-' + stat_id).css('background-image', '') .addClass('svg-icon library-' + library_type); diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 6d8b3aaf..87c8f9d8 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -12,8 +12,10 @@ data :: Usable parameters (if not applicable for media type, blank value will be == Global keys == rating_key Returns the unique identifier for the media item. media_type Returns the type of media. Either 'movie', 'show', 'season', 'episode', 'artist', 'album', or 'track'. +sub_media_type Returns the subtype of media. Either 'movie', 'show', 'season', 'episode', 'artist', 'album', or 'track'. art Returns the location of the item's artwork title Returns the name of the movie, show, episode, artist, album, or track. +edition_title Returns the edition title of a movie. duration Returns the standard runtime of the media. content_rating Returns the age rating for the media. summary Returns a brief description of the media plot. @@ -212,7 +214,7 @@ DOCUMENTATION :: END % if _session['user_group'] == 'admin': % endif - % elif data['media_type'] in ('artist', 'album', 'track', 'playlist', 'photo_album', 'photo', 'clip'): + % elif data['media_type'] in ('artist', 'album', 'track', 'playlist', 'photo_album', 'photo', 'clip') or data['sub_media_type'] in ('artist', 'album', 'track'):
@@ -266,7 +268,7 @@ DOCUMENTATION :: END

${data['parent_title']}

${data['title']}

% elif data['media_type'] == 'track': -

${data['original_title'] or data['grandparent_title']}

+

${data['grandparent_title']}

${data['parent_title']} - ${data['title']}

% elif data['media_type'] in ('photo', 'clip'): @@ -282,14 +284,14 @@ DOCUMENTATION :: END padding_height = '' if data['media_type'] == 'movie' or data['live']: padding_height = 'height: 305px;' - elif data['media_type'] in ('show', 'season', 'collection'): - padding_height = 'height: 270px;' - elif data['media_type'] == 'episode': - padding_height = 'height: 70px;' - elif data['media_type'] in ('artist', 'album', 'playlist', 'photo_album', 'photo'): + elif data['media_type'] in ('artist', 'album', 'playlist', 'photo_album', 'photo') or data['sub_media_type'] in ('artist', 'album', 'track'): padding_height = 'height: 150px;' elif data['media_type'] in ('track', 'clip'): padding_height = 'height: 180px;' + elif data['media_type'] == 'episode': + padding_height = 'height: 70px;' + elif data['media_type'] in ('show', 'season', 'collection'): + padding_height = 'height: 270px;' %> +
+ % if data['media_type'] == 'track' and data['original_title']: + Track Artists ${data['original_title']} + % endif +
% if data['media_type'] == 'movie': Year ${data['year']} @@ -390,6 +397,11 @@ DOCUMENTATION :: END Runtime ${data['duration']} % endif
+ % if data['edition_title']: +
+ Edition ${data['edition_title']} +
+ % endif
% if data['content_rating']: Rated ${data['content_rating']} @@ -542,7 +554,7 @@ DOCUMENTATION :: END
% endif - % if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track'): + % if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track', 'collection', 'playlist'):
@@ -571,7 +583,7 @@ DOCUMENTATION :: END
% endif <% - history_type = data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track') + history_type = data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track', 'collection', 'playlist') history_active = 'active' if history_type else '' export_active = 'active' if not history_type else '' %> @@ -634,7 +646,7 @@ DOCUMENTATION :: END
- % if data['media_type'] in ('artist', 'album', 'track'): + % if data['media_type'] in ('artist', 'album', 'track', 'playlist'): Play History for ${data['title']} % else: Watch History for ${data['title']} @@ -680,7 +692,7 @@ DOCUMENTATION :: END Started Paused Stopped - Duration + Duration @@ -806,7 +818,7 @@ DOCUMENTATION :: END % elif data['media_type'] == 'album': ${data['parent_title']}
${data['title']} % elif data['media_type'] == 'track': - ${data['original_title'] or data['grandparent_title']}
${data['title']}
${data['parent_title']} + ${data['grandparent_title']}
${data['title']}
${data['parent_title']} % endif

@@ -853,7 +865,7 @@ DOCUMENTATION :: END %> -% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track'): +% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track', 'collection', 'playlist'): % endif -% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track'): +% if data['media_type'] in ('movie', 'show', 'season', 'episode', 'artist', 'album', 'track', 'collection', 'playlist'):