Compare commits

...

778 Commits

Author SHA1 Message Date
Jason Dove
f0f2b3da4b update changelog for release v0.6.5-beta [no ci] 2022-08-02 07:36:45 -05:00
Jason Dove
866049543c fix db initializer (#907) 2022-07-31 12:30:33 -05:00
Jason Dove
40ed4b8b0e update changelog for release v0.6.4-beta [no ci] 2022-07-28 12:23:33 -05:00
Jason Dove
b43d08ca67 fix repeating schedules (#901) 2022-07-26 13:04:05 -05:00
Jason Dove
5e7e386108 fix search result filtering for episodes and other videos (#900) 2022-07-25 20:02:07 -05:00
Jason Dove
4176df9940 fix nvidia capabilities for second-gen maxwell (#899) 2022-07-24 12:23:30 -05:00
Jason Dove
de2ef959fe add 640x480 resolution (#898)
* update dependencies

* add 640x480 resolution
2022-07-24 08:17:27 -05:00
Jason Dove
b53cfebac1 fix bug with unsupported aac channel layouts (#893)
* fix bug with unsupported aac channel layouts

* update dependencies
2022-07-14 10:52:25 -05:00
Jason Dove
6895b9cc6b fix search repo caching bug (#886)
* add failing test

* fix search repo bug

* update dependencies
2022-07-10 15:32:06 -05:00
Jason Dove
c60d6e46f1 fix changelog [no ci] 2022-07-04 15:23:26 -05:00
Jason Dove
c66d190174 update changelog for release v0.6.3-beta [no ci] 2022-07-04 15:20:53 -05:00
Jason Dove
5e8da591be update dependencies (#883)
* fix database initialization

* update dependencies
2022-07-02 20:42:07 -05:00
Jason Dove
9c02a6738b fix missing trashed episodes (#881)
* fix episodes missing from trash

* cleanup
2022-06-29 15:01:49 -05:00
Jason Dove
5ed0184bca add minimum log level setting (#877) 2022-06-27 10:29:04 -05:00
Jason Dove
ae64ca4a93 fix arm images by using ls55 (#876) 2022-06-26 17:41:39 -05:00
Jason Dove
c47099895e include item state in search index duplicate filter (#875) 2022-06-26 13:34:54 -05:00
Jason Dove
a2529febba use brew for gon 2022-06-26 08:30:32 -05:00
Jason Dove
521e0ba8b3 get a new build 2022-06-26 06:46:30 -05:00
Jason Dove
ee0efac9be only publish docs when docs are updated 2022-06-26 06:21:27 -05:00
Jason Dove
bfe7635489 work around github actions issue on mac (#874) 2022-06-25 19:30:21 -05:00
Jason Dove
aa1735f024 fix song and other video search index (#873) 2022-06-25 18:13:39 -05:00
Jason Dove
8deae983c7 add some startup log messages (#872) 2022-06-25 13:03:37 -05:00
Jason Dove
f349646703 apply plex episode metadata updates (#871)
* update more plex episode metadata

* update dependencies
2022-06-22 19:41:05 -05:00
Jason Dove
5003e80500 maintain stream continuity after playout reset (#868)
* maintain stream continuity after playout reset

* maintain continuity after error streams
2022-06-18 21:38:25 -05:00
Jason Dove
940d9cd6b5 update changelog for release v0.6.2-beta [no ci] 2022-06-18 13:46:45 -05:00
Jason Dove
197c166789 fix jellyfin admin id selection (#867) 2022-06-17 18:25:22 -05:00
Jason Dove
d114db091e use proper nvidia accel output format for 10-bit content (#865) 2022-06-17 11:33:10 -05:00
Jason Dove
3204da8e43 adjust nvidia capabilities (#864)
* adjust nvidia capabilities logic

* fallback to software encoding for 10-bit h264

* cleanup

* more tweaks
2022-06-17 10:50:36 -05:00
Jason Dove
100eb14408 fix epg sorting (#863)
* fix epg sorting

* update dependencies
2022-06-17 08:44:26 -05:00
Jason Dove
025017ace5 regularly delete old segments (#856)
* regularly delete old segments

* cleanup
2022-06-15 21:12:07 -05:00
Jason Dove
6a690c7c10 add more filler logging (#854) 2022-06-15 10:21:05 -05:00
Jason Dove
dd7f77751c detect nvidia capabilities (#853)
* fallback to software codecs for old nvidia cards

* update dependencies
2022-06-14 19:44:34 -05:00
Jason Dove
0c13b8ef1a force amd64 for arm32v7 sdk build layer (#843) 2022-06-12 13:54:55 -05:00
Jason Dove
c6ca58ab97 build arm32v7 docker image (#842)
* build arm32v7 docker image

* fix
2022-06-12 13:42:27 -05:00
Jason Dove
0846fc1d96 update workflow dependencies (#841) 2022-06-11 13:40:47 -05:00
Jason Dove
e41dd68ee0 fix automatic playout building (#840) 2022-06-11 13:11:04 -05:00
Jason Dove
0a92996da8 fix repeating content (#838)
* fix repeating content

* update dependencies
2022-06-08 10:37:56 -05:00
Jason Dove
082bc6145c update changelog for release v0.6.1-beta [no ci] 2022-06-03 15:05:06 -05:00
Jason Dove
bf3f16451b music video credits tweaks (#834)
* fix song subtitles

* always use generated subtitles

* file not found/unavailable fixes
2022-06-03 14:44:52 -05:00
Jake
3cb37003cb UI rewrite - ffmpeg profiles (#823)
* ffmpeg profile functionality, sweetalert2

* add new files

* cleanup controller; remove unused classes

* apply formatting

* cleanup core project

* don't use bom

* whitespace

* remove generated css

* remove generated js/map

* Remove attempted linter fix, channels button, watermarks page. Fixed handlerror.

* Changed deleted confirmation to toast.

* Localized strings for language change. Modified Action icons to buttons and left default sizes. Changed Cancel to No where Yes is an option

* lint

Co-authored-by: Jason Dove <jason@jasondove.me>
2022-06-03 06:28:32 -05:00
Jason Dove
9acfd2cd06 fix plex server identification (#833) 2022-06-03 05:53:41 -05:00
dependabot[bot]
3242e7ebb8 Bump HtmlSanitizer from 7.1.488 to 7.1.509 (#830)
Bumps [HtmlSanitizer](https://github.com/mganss/HtmlSanitizer) from 7.1.488 to 7.1.509.
- [Release notes](https://github.com/mganss/HtmlSanitizer/releases)
- [Commits](https://github.com/mganss/HtmlSanitizer/compare/v7.1.488...v7.1.509)

---
updated-dependencies:
- dependency-name: HtmlSanitizer
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-02 21:21:17 -05:00
Jason Dove
7644d628e7 generate music video credits (#832) 2022-06-02 20:50:55 -05:00
Jason Dove
b4f19e6de4 fix jellyfin tv paging (#831) 2022-06-02 12:16:59 -05:00
Jason Dove
0388425763 update changelog for release v0.6.0-beta [no ci] 2022-06-01 18:01:07 -05:00
Jason Dove
ca5d303ac7 fix qsv encoder regression and memory errors (#828)
* fix qsv encoders; only use 64 extra hw frames

* update changelog
2022-05-31 06:07:50 -05:00
Jason Dove
18e66a92ad add paging to media server show and collection calls (#827)
* add paging to media server show library calls

* add paging to media server season and episode library calls

* formatting

* add paging to media server collection calls

* add paging to media server collection item calls

* update changelog
2022-05-31 05:56:48 -05:00
Jason Dove
7d0a56ab98 disable lower-power mode for qsv encoders (#826) 2022-05-29 20:44:27 -05:00
Jason Dove
5069792d12 update dependencies 2022-05-28 20:41:54 -05:00
Jason Dove
c9789458b9 page media server movie libraries 2022-05-28 20:41:22 -05:00
Jason Dove
777a0d09ed hls segmenter fixes (#824)
* fix pts warning when channel first starts streaming

* rework playlist filtering
2022-05-25 21:05:55 -05:00
Jason Dove
4e2ebcc48a fix watermark opacity filter (#820) 2022-05-23 10:29:04 -05:00
Jason Dove
90fe1d7709 fix hw accel health check for qsv in vaapi docker (#818) 2022-05-22 19:42:14 -05:00
Jason Dove
1576dd026e enable qsv accel for vaapi docker images (#817) 2022-05-22 18:43:24 -05:00
Jason Dove
ee7a64eea9 fix other video libraries (#816)
* update depdendencies

* reset other video libraries
2022-05-22 12:29:35 -05:00
Jason Dove
9742e1eef7 update changelog for release v0.5.8-beta [no ci] 2022-05-20 09:11:16 -05:00
Jason Dove
a61c4b3472 fix a handful of scheduling edge cases (#814) 2022-05-18 06:00:58 -05:00
Jason Dove
ea0d43cf99 use hardware acceleration for error messages (#813)
* logging fixes

* use hardware acceleration for error messages
2022-05-18 05:44:01 -05:00
Jason Dove
fd36ea51a7 unlock ffmpeg thread count (#812)
* revert logging changes

* unlock ffmpeg thread count
2022-05-17 21:37:44 -05:00
Jason Dove
5213b71d62 add debug logging to track down playlist filtering issue (#811)
* add debug logging to track down playlist filtering issue

* revert work-ahead change
2022-05-17 15:18:12 -05:00
Jason Dove
0ba3ac7f50 fix more error stream settings (#810) 2022-05-17 11:44:12 -05:00
Jason Dove
d960fec734 error stream needs video track timescale (#809) 2022-05-17 10:26:46 -05:00
Jason Dove
f272036c6f reduce hls disk use (#808)
* reduce hls segmenter disk use

* logging improvements

* update dependencies
2022-05-17 08:43:28 -05:00
Jason Dove
9fe03b6ef3 reduce hls segmenter disk use (#806) 2022-05-16 21:45:13 -05:00
Jason Dove
f895ab5304 fix nuget versions 2022-05-14 06:37:22 -05:00
Jason Dove
07c54ff45f update changelog for release v0.5.7-beta [no ci] 2022-05-14 05:54:27 -05:00
Jason Dove
6a29ce2049 update dependencies (#805) 2022-05-13 21:16:43 -05:00
Jason Dove
d19e95fb38 add random start point option (#804) 2022-05-13 20:36:03 -05:00
Jason Dove
d78daf8735 fix flood checkpoints (#803) 2022-05-13 15:31:04 -05:00
Jason Dove
4f6522379d fix custom title scheduling (#802) 2022-05-13 13:06:05 -05:00
Jason Dove
9e0972fec0 properly ignore plex other videos libraries (#801) 2022-05-13 12:31:34 -05:00
Jason Dove
6d564233ed filler scheduling fix (#800) 2022-05-12 14:02:06 -05:00
Jason Dove
47252b1243 read track from music video nfo metadata (#799) 2022-05-12 12:31:40 -05:00
Jason Dove
bd5b52922d add option to allow watermarks over filler (#796) 2022-05-09 17:51:11 -05:00
Jason Dove
59c793b9be add option to skip missing items in playouts (#795) 2022-05-09 09:21:51 -05:00
Jason Dove
3ad1ba01f8 add autocomplete to search bar (#791) 2022-05-08 19:58:15 -05:00
Jason Dove
ab10f0ed81 add metadata_kind to search index (#790)
* more nfo cleanup

* add metadata_kind to search index
2022-05-07 21:24:50 -05:00
Jason Dove
44dd68fe59 nfo and memory fixes (#789)
* partial episode nfo metadata

* nfo metadata reliability fixes

* use recyclable memory streams
2022-05-07 20:32:57 -05:00
Jason Dove
6326189444 update changelog for release v0.5.6-beta [no ci] 2022-05-06 12:42:57 -05:00
Jason Dove
198c693208 fix other video fallback metadata 2022-05-05 20:51:24 -05:00
Jason Dove
1431b33a98 support movie nfo metadata in other video libraries (#788)
* add other video nfo metadata

* update docs
2022-05-05 20:38:23 -05:00
Jason Dove
e81a8e58ea fix error continuity (#787)
* fix fallback filler playback

* use new transcoder logic for errors

* use realtime option for error streams
2022-05-05 13:31:09 -05:00
Jason Dove
daf7114ce2 bug fixes and logging (#786) 2022-05-05 10:04:24 -05:00
Jason Dove
8542bc20b1 update dependencies (#785) 2022-05-04 20:45:19 -05:00
Jason Dove
9decb91bf7 use aired for music video release date (#784) 2022-05-04 11:36:00 -05:00
Jason Dove
fcfd579b37 fix search index validation (#782) 2022-05-03 22:26:44 -05:00
Jason Dove
e9be182bed bug fixes and search tweaks (#781)
* fix movie nfo processing

* fix local movie fallback metadata

* use imagesharp again

* fix search edge case

* add show_genre and show_tag to search index
2022-05-03 21:58:39 -05:00
Jason Dove
610e261cd7 update changelog again [no ci] 2022-05-03 10:23:57 -05:00
Jason Dove
f65818c838 update changelog for release v0.5.5-beta [no ci] 2022-05-03 10:22:10 -05:00
Jason Dove
1651d2895e update dependencies 2022-05-03 09:45:25 -05:00
Jason Dove
b90c536dcb try to fix quemu condition 2022-05-03 09:33:02 -05:00
Jason Dove
5c98eb3df5 more conditions 2022-05-02 22:46:22 -05:00
Jason Dove
bdff5eba75 fix conditions 2022-05-02 22:41:11 -05:00
Jason Dove
7d112eda05 fix tag 2022-05-02 22:28:54 -05:00
Jason Dove
4f16431ca0 use specific arm64v8 tags 2022-05-02 22:14:49 -05:00
Jason Dove
69b39c6940 try building arm64 docker image 2022-05-02 22:10:50 -05:00
Jason Dove
fe7181ea1d workflow fixes 2022-05-02 21:47:37 -05:00
Jason Dove
88b287a094 use matrix for docker build workflow 2022-05-02 21:44:35 -05:00
Jason Dove
7953e3ad85 actually fix windows tests [no ci] 2022-05-02 13:41:05 -05:00
Jason Dove
8ba6374165 music video nfo multiple artists (#780)
* support multiple artist entries in music video nfo metadata

* clean up other video and song etags
2022-05-02 12:32:15 -05:00
Jason Dove
973dd4b53d try to fix tests on windows again [no ci] 2022-05-02 05:54:56 -05:00
Jason Dove
6facd745ec fix extracting embedded mov_text subtitles (#777)
* fix extracting embedded mov_text subtitles

* changelog

* cleanup
2022-05-01 21:24:14 -05:00
Jason Dove
32c4c4ec8b fix failing tests on windows [no ci] 2022-05-01 14:11:55 -05:00
Jason Dove
ecb6ed37f0 more local metadata parsing improvements (#776)
* extract remaining nfo xml serializers

* add artist nfo tests

* add music video nfo tests

* add tv show nfo reader tests

* custom artist nfo reader

* custom music video nfo reader

* custom tv show nfo reader

* local metadata parsing cleanup

* update changelog
2022-05-01 14:00:10 -05:00
Jason Dove
2a8bf57930 ignore emby and jellyfin path infos with unset network path (#775) 2022-04-30 21:48:37 -05:00
Jason Dove
1ebc4b62e3 bug fixes (#774)
* add custom movie metadata parsing

* refactor episode nfo reader

* fix emby and jellyfin bugs
2022-04-30 17:39:47 -05:00
Jason Dove
4ae671b633 fix trashing episodes with no title (#773) 2022-04-29 21:49:23 -05:00
Jason Dove
87aa69f4cc update changelog for release v0.5.4-beta [no ci] 2022-04-29 17:59:12 -05:00
Jason Dove
404ea49e35 jellyfin and emby path infos (#771)
* start adding jellyfin path info; fix some scanning bugs

* sync jellyfin libraries before scanning to ensure latest path infos

* code cleanup

* support emby path infos

* fix periodic scanning of emby and jellyfin libraries

* bug fixes
2022-04-29 15:07:17 -05:00
Jason Dove
4ed40acfbe rebuild corrupt search index (#770) 2022-04-28 13:49:06 -05:00
Jason Dove
17f540dc99 add more search fields (#769) 2022-04-28 10:40:27 -05:00
Jason Dove
780ebc01ee add v2 ffmpeg profile page (#768)
* wip

* remove transcode property; use i18n

* add api

* use computed table headers for i18n
2022-04-28 06:56:01 -05:00
Jason Dove
0a0fb71b94 refactor plex, emby and jellyfin television scanners (#767)
* refactor plex television scanner

* refactor emby television scanner

* refactor jellyfin television scanner

* update changelog
2022-04-27 22:34:25 -05:00
Jason Dove
53d6ecae8d fix windows build 2022-04-27 14:20:14 -05:00
Jason Dove
837f311ec0 add more search fields (#766)
* properly index show and season state

* add height, width, season_number, episode_number to search index

* update docs
2022-04-27 13:58:33 -05:00
Jason Dove
a9a89d04ea optimize search-index rebuilding (#765)
* update dependencies

* optimize search-index rebuilding

* cleanup logging
2022-04-27 12:23:37 -05:00
Rafael Vieira
2e1073eb53 Add support to internationalization (#764)
* client-app: Improve development documentation

* client-app: add basic support to translation

* client-app: fix i18n and create lang state

* client-app: add language selector

* client-app: add translation EN and PT-BR
2022-04-27 10:58:27 -05:00
Jason Dove
7687278b80 health check fixes (#763) 2022-04-26 09:38:03 -05:00
Jason Dove
392aebd46f refactor movie library scanners (#761)
* catch health check cancellation

* local library scanner cleanup

* emby and jf library scanner cleanup

* rework emby movie library scanner

* refactor emby movie library scanner

* refactor jellyfin movie library scanner

* clear etag until after new media has been processed

* refactor plex movie library scanner

* update changelog
2022-04-25 20:31:12 -05:00
Jason Dove
0a4f6d9b62 update changelog for release v0.5.3-beta [no ci] 2022-04-24 13:45:29 -05:00
Jason Dove
d879ce0d0d bug fixes (#757)
* fix docker blur hash generation

* scanner async cleanup

* catch and log some unauthorized exception errors
2022-04-24 13:43:06 -05:00
Jason Dove
558e8acf5f unavailable improvements (#756)
* add unavailable health check

* improve file not found health check
2022-04-24 11:59:41 -05:00
Jason Dove
89a2ac9455 add unavailable media state for plex media (#754)
* rework plex movie library scanner; add unavailable media item state

* plex television scanner improvements

* reset plex etags as needed

* update changelog
2022-04-23 22:19:10 -05:00
Jason Dove
39c05a24d8 update changelog for release v0.5.2-beta [no ci] 2022-04-22 19:33:42 -05:00
Jason Dove
78383bd5fa override languages and subtitles on schedule items (#753) 2022-04-22 15:45:54 -05:00
Jason Dove
d67251bfa0 jellyfin and emby collection sync (#752)
* sync jellyfin and emby collections

* update changelog
2022-04-22 13:33:35 -05:00
Jason Dove
e91ec98007 fix season sync from jellyfin and emby (#751) 2022-04-21 21:36:09 -05:00
Jason Dove
097b8c3d1f subtitle fixes (#750)
* fix crash with missing metadata

* fix subtitles in docker

* fix software overlay bug
2022-04-21 20:17:50 -05:00
Jason Dove
7284ee9fb7 fix updating local season metadata 2022-04-21 15:42:10 -05:00
Jason Dove
fccb9003a0 add plex deep scan mode and sync labels (#749) 2022-04-21 14:02:37 -05:00
Jason Dove
cc9c2f6ae3 fix external subtitles with brackets in the filename (#748) 2022-04-21 10:25:21 -05:00
Jason Dove
ec6eab97b2 plex scanner improvements (#747)
* plex api cleanup

* improve plex movie scanner

* sync plex collections as tags

* improve plex tv library scanner

* update dependencies

* fix plex season and episode collection tags
2022-04-21 09:54:38 -05:00
Jason Dove
3ede136ff3 fix windows build 2022-04-20 16:10:13 -05:00
Jason Dove
7c27241ab6 properly reset emby and jellyfin libraries 2022-04-20 15:56:48 -05:00
Jason Dove
3713711864 support external subtitles (#745)
* support external subtitles in local movie libraries

* code cleanup

* simplify subtitle updating

* skip external subtitles from media servers

* fix plex subtitles
2022-04-20 15:54:53 -05:00
Jason Dove
d755d0ae29 sync subtitles from media server scanners (#744) 2022-04-19 21:24:36 -05:00
Jason Dove
c02b83d0d6 code cleanup (#743)
* update tools

* run code cleanup

* update dependencies
2022-04-19 17:47:18 -05:00
Jason Dove
e250e93a8e add support for embedded text subtitles (#742)
* first pass at text subtitle support

* support text subtitles from movies, music videos and other videos

* fixes

* qsv fixes

* vaapi fixes

* update changelog
2022-04-19 12:57:24 -05:00
Jason Dove
60965d0961 update changelog for release v0.5.1-beta [no ci] 2022-04-17 17:36:32 -05:00
Jason Dove
ff1a7b376f add empty trash button (#739) 2022-04-17 14:45:49 -05:00
Jason Dove
741b00fd52 fix multiple filler scheduling bugs (#738) 2022-04-17 13:30:47 -05:00
Jason Dove
7e55681916 fix cliwrap usage (#737) 2022-04-16 20:12:18 -05:00
Jason Dove
210630d299 subtitle fixes for software, videotoolbox, vaapi (#736)
* fix subtitles using software encoders

* videotoolbox fixes

* fix some vaapi subtitle edge cases
2022-04-16 16:06:49 -05:00
Jason Dove
0ddbb898d6 fix subtitle stream selection (#735) 2022-04-15 19:40:08 -05:00
Jason Dove
d6bf579436 fix alpha => beta versioning 2022-04-15 09:08:30 -05:00
Jason Dove
765df64555 add picture subtitle transcoding tests, and make them all pass with nvenc (#734) 2022-04-14 22:30:26 -05:00
Jason Dove
8764fb93fa update for release v0.5.0-beta [no ci] 2022-04-13 11:01:54 -05:00
Jason Dove
7d5c3e2384 update docs 2022-04-13 11:01:49 -05:00
Jason Dove
1ee3446589 add schedule item watermark setting (#733) 2022-04-12 21:01:20 -05:00
Jason Dove
af39d93442 more scheduling fixes (#732)
* fix skipping days with fixed start times

* fix playouts getting "stuck" on the same items

* rebuild all playouts

* update dependencies
2022-04-12 09:19:13 -05:00
Jason Dove
5d6a6d3a76 fix schedule anchors (#726) 2022-04-10 11:29:50 -05:00
Jason Dove
1f27aef11d allow two decimals in channel numbers (#724) 2022-04-05 20:44:50 -05:00
Jason Dove
ddb6d99cf9 remove sqlite startup messages (#723) 2022-04-03 23:37:38 -05:00
Jason Dove
d54766866e optimize image manipulation (#722)
* update dependencies

* use ffmpeg to resize images

* use ffprobe to check for animated logos and watermarks

* remove last use of imagesharp
2022-04-03 22:43:11 -05:00
Jason Dove
25bc500a2b ensure HDHR clients always get an MPEG-TS stream (#721) 2022-04-03 18:03:34 -05:00
Jason Dove
c2eec2fc2d playout rework to maintain collection progress (#720)
* initial work on maintaining playout state

* debugging wip

* fix refresh playout logic

* fix failing test

* more fixes

* update changelog

* comment out some debug logs

* comment out more logs
2022-04-02 21:19:59 -05:00
Jason Dove
f9781a4c05 detect and handle nonzero hls segmenter exit code (#719) 2022-04-01 20:27:40 -05:00
Jason Dove
df45b93819 burn in picture-based subtitles (#718)
* add subtitle mode setting

* start to add subtitle support

* cuda test

* move subtitle settings from ffmpeg profile to channel

* fix image-based subtitles

* experimental wip

* subtitle fixes
2022-03-31 18:15:57 -05:00
Jason Dove
e697fd36e9 remove legacy transcoder logic option (#717) 2022-03-31 16:00:50 -05:00
Jason Dove
0308106c1b update changelog for release v0.4.5-alpha [no ci] 2022-03-29 20:12:01 -05:00
Jason Dove
ba93e3eeea always check for plex on the localhost (#716) 2022-03-29 19:39:55 -05:00
Jason Dove
aa2c914d8a always transcode and normalize, except with HLS Direct (#715)
* remove transcode, normalize video, normalize audio settings

* cleanup

* update changelog
2022-03-28 18:25:33 -05:00
Jason Dove
caa9bf82d5 update changelog [no ci] 2022-03-28 13:10:33 -05:00
Jason Dove
e397035c5a update changelog [no ci] 2022-03-18 14:07:15 -05:00
Jason Dove
d32f881c4e use cliwrap for windows wrapper (#710) 2022-03-18 13:36:38 -05:00
Jason Dove
9b3c24559d add deinterlace option to ffmpeg profile (#709)
* update dependencies

* add option to deinterlace video
2022-03-18 10:30:56 -05:00
Jason Dove
7c75b169ec try gon again 2022-03-16 11:39:44 -05:00
Jason Dove
4f1952340f fix gon since brew is not working 2022-03-16 11:24:52 -05:00
Jason Dove
ac2de24f6e fix build 2022-03-16 10:50:38 -05:00
Jason Dove
4b9781dad4 add v2 channels table (#708)
* change mock api port; fix cors

* fix version request

* accept base url env var

* tweak colors

* fix version service

* add basic channels list

* fix auto page title for ts classes

* add GET /api/channels
2022-03-16 10:28:54 -05:00
Jason Dove
d88c179b63 axios test (#705)
* add version controller

* use axios to get version

* allow typescript

* use version api service
2022-03-15 17:55:05 -05:00
Jason Dove
809a623a95 vue formatting tweaks (#704)
* add .prettierrc

* apply formatting
2022-03-14 10:59:04 -05:00
Jason Dove
b453dce57e try npm ci with cache 2022-03-13 20:38:04 -05:00
Jason Dove
edd31755c0 try npm install again 2022-03-13 20:21:16 -05:00
Jason Dove
7de1a87bbf bug fixes (#703)
* catch expected shutdown error in scheduler service

* fix streaming mode inconsistencies
2022-03-13 18:45:21 -05:00
Jason Dove
5731edc82e v2 ui tweaks (#702)
* fix m3u and xml urls (kind of)

* use svg logos

* tweak theme colors
2022-03-13 12:44:35 -05:00
Jason Dove
2f668e53dd fix npm build (#701) 2022-03-13 11:11:08 -05:00
Jason Dove
abd223acd2 fix docker builds (#700) 2022-03-13 10:52:03 -05:00
Jason Dove
6a9075dc11 add vue ui at /v2 (#698)
* add vue ui

* add channels mock api

* Initial Vue framework with Vuetify UI (#688)

* fix hls direct streaming mode (#682)

* duration analysis on files with missing duration metadata (#683)

* first pass

* analyze zero-duration files

* add readme note for WIP

* add vuetify and basic sidebar layout - responsive

* add vue-router and initial home page

* setup composition-api for vue2

* install pinia ie... vuex4

* mixing for automatic page title

* add logo files

* tweak theme colors

* install store

* use store for menu toggle

* replicate old menu

* implement menu and children menus

* rename state to application state

* update vue files and add version to sidebar

* lock logo and make expandable list remove minified menu

* remove todo, will add to PR

* top bar links and attempt at snackbar with state

* fix snackbar

* add search basic component

* fix search bar placement

* remove un-used footer

* Revert "Merge branch 'jasongdove:main' into intitial-vuetify-ui"

This reverts commit 43016d502b.

Co-authored-by: Jason Dove <jason@jasondove.me>

* Add ESLint and Prettier for VueJS (#691)

* add prettier to config

* run npm run lint over project to clean up files

* replace hr tag with v-dividers

* add vue-lint github action

* fix the dodo in me

* Hu

* Fix path

* convert to multi-run step

* forgot the name

* add vue-lint github action

fix the dodo in me

Hu

Fix path

convert to multi-run step

forgot the name

* Fix new line at end of file

* WIP

* dockerfile consistency

* use npm ci and node v14

* force prettier indenting and end of line (#695)

* disable filename hashing

* don't build tests in docker

* update dependencies

* fix running both uis

Co-authored-by: James Mackay <info@notexpectedyet.com>
2022-03-12 14:15:31 -06:00
Jason Dove
d5a03963c0 use video and audio format instead of video and audio codec (#696) 2022-03-11 19:58:05 -06:00
Jason Dove
f3e5ff198b update changelog for release v0.4.4-alpha [no ci] 2022-03-10 17:54:39 -06:00
Jason Dove
6be5111195 fix errors; add nouveau vaapi driver option (#692)
* fix service shutdown errors

* add nouveau vaapi driver option
2022-03-10 12:19:30 -06:00
Jason Dove
f0670b345f use wrapped processes; fix hls pts bug (#690) 2022-03-09 14:38:09 -06:00
Jason Dove
6a1c2b7659 read ffprobe pts output from stdout and stderr (#689) 2022-03-09 08:54:51 -06:00
Jason Dove
7cd2f9a56f update dependencies (#687) 2022-03-08 20:21:10 -06:00
Jason Dove
f66bc783a7 fix hls segmenter on windows (#686) 2022-03-08 19:58:41 -06:00
Jason Dove
bc225d35fa add troubleshooting code to hls segmenter (#685) 2022-03-08 12:52:29 -06:00
Jason Dove
52a8b7db81 duration analysis on files with missing duration metadata (#683)
* first pass

* analyze zero-duration files
2022-03-07 21:42:34 -06:00
Jason Dove
dcd792a354 fix hls direct streaming mode (#682) 2022-03-07 20:04:21 -06:00
Jason Dove
f69c58c6bf update changelog for release v0.4.3-alpha [no ci] 2022-03-05 19:49:31 -06:00
Jason Dove
44e90b0ecc more bug reporting (#679) 2022-03-05 18:21:53 -06:00
Jason Dove
dcc8f19a6b remove transient IDbConnection (#678) 2022-03-05 14:06:27 -06:00
Jason Dove
fc1a051df5 fix path replacement bug (#677) 2022-03-05 12:21:26 -06:00
Jason Dove
a2e7e6df1e fix thread sync bug in hls segmenter (#676) 2022-03-05 11:26:29 -06:00
Jason Dove
6c06fbe621 fix mid-roll filler scheduling bug (#675) 2022-03-04 21:40:08 -06:00
Jason Dove
a3260b2316 fix album_artist metadata (#674) 2022-03-04 21:10:55 -06:00
Jason Dove
ea339a1622 add song album_artist metadata (#673) 2022-03-04 18:44:54 -06:00
Jason Dove
58697496fa metadata stack trace improvements (#672)
* improve stack traces from local metadata provider

* more metadata line number fixes
2022-03-04 15:44:51 -06:00
Jason Dove
ec1b2502f1 rework bugsnag integration (#671) 2022-03-03 21:39:46 -06:00
Jason Dove
1ab98578ab refactor namespaces and imports (#670)
* re-namespace

* optimize usings

* more usings

* more of the same

* more implicit/global usings

* cleanup all usings

* minor fixes
2022-03-03 15:36:07 -06:00
Jason Dove
748581bf5a update dependencies (#669) 2022-03-03 08:46:08 -06:00
Jason Dove
2058c44949 minor ui fixes (#668)
* remove unused package

* fix controller name

* catch and ignore jsruntime exceptions

* separate debug stage from public stage
2022-03-02 20:45:07 -06:00
Jason Dove
24ef5e68eb add error reporting health check (#667) 2022-03-02 14:51:15 -06:00
Jason Dove
b1c905233f add thank you page to docs [no build] 2022-03-02 09:19:54 -06:00
Jason Dove
452f361384 use cancellation tokens in ui (#666)
* use cancellation tokens in pages

* use cancellation token in shared ui

* use cancellation tokens in artwork controller
2022-03-01 21:31:48 -06:00
Jason Dove
9e2f445785 add bugsnag error reporting (#665) 2022-03-01 18:41:24 -06:00
Jason Dove
ea72e7b689 use new transcoder logic by default (#664) 2022-03-01 14:29:47 -06:00
Jason Dove
19a7f90d52 ffmpeg lib hardware accel fixes (#663)
* more scaling fixes

* qsv fixes

* cleanup

* nvidia fixes

* vaapi fixes

* test nvidia by default
2022-03-01 14:03:36 -06:00
Jason Dove
572a3be33e limit framerate normalization to 24fps and above (#662) 2022-02-28 19:22:11 -06:00
Jason Dove
f8412c4f5c more nvidia fixes (#661)
* dont use mpeg2_cuvid with interlaced content

* fix nvidia scaling when padding is not needed
2022-02-28 18:35:49 -06:00
Jason Dove
6b0ced6be9 fix nvidia watermark (#660)
* add watermark transcoding tests

* nvidia fixes

* update changelog
2022-02-28 14:21:42 -06:00
Jason Dove
19161b12ea fix chronological and shuffle-in-order song sorting (#659) 2022-02-28 13:41:22 -06:00
Jason Dove
fd3ef90880 update changelog for release v0.4.2-alpha [no ci] 2022-02-26 19:23:18 -06:00
Jason Dove
696b29c9e9 fix videotoolbox acceleration with new transcoder (#658)
* fix videotoolbox acceleration with new transcoder

* cleanup
2022-02-26 18:45:05 -06:00
Jason Dove
70c37df596 fix vaapi watermarks with new transcoder (#657) 2022-02-26 14:00:23 -06:00
Jason Dove
040785b0d7 fix qsv scaling and watermarks with new transcoder (#656)
* fix qsv scaling

* fix qsv watermarks
2022-02-26 11:40:16 -06:00
Jason Dove
b25f783343 hide unused local libraries (#655) 2022-02-25 21:16:38 -06:00
Jason Dove
a21f62ff8c add watermark support to experimental transcoder logic (#654)
* wip

* fix songs again

* don't use lists of video and audio input files

* test concat command output

* move concat pipeline

* start to use ffmpeg state

* add ffmpeg state and audio state

* audio state is required

* attach input options directly to input files

* move filters to input files

* add watermark support
2022-02-25 21:06:17 -06:00
Jason Dove
78fdc9c57a add option to shuffle schedule items (#652)
* add schedule setting

* it works

* fix tests

* update readme

* rebuild all playouts
2022-02-22 21:20:40 -06:00
Jason Dove
f6c42f3ff5 add configurable channel group and categories (#651) 2022-02-21 21:04:12 -06:00
Jason Dove
c92b6cb909 fix song playback (#644) 2022-02-17 21:52:28 -06:00
Jason Dove
a2e1dc8bfb don't deinterlace using nvidia decoders (#643) 2022-02-17 13:28:03 -06:00
Jason Dove
8a6093ce8d properly specify audio codec even when source has correct format (#641) 2022-02-16 11:29:54 -06:00
Jason Dove
1d6279cee8 log problematic playlists (#640) 2022-02-16 08:18:22 -06:00
Jason Dove
66ab0b3990 use single thread and disable framerate normalization (#639)
* try one thread for everything

* add (unused) framerate filter

* disable framerate normalization by default

* update dependencies
2022-02-16 06:01:28 -06:00
Jason Dove
a7922beaed qsv and logging fixes (#637)
* improve pipeline logging

* fix qsv acceleration

* fix qsv parameter order
2022-02-15 12:41:06 -06:00
Jason Dove
a1d9d6790e fix copy codec when transcoding is disabled (#636) 2022-02-15 09:22:40 -06:00
Jason Dove
2f2d7952dd use vaapi driver settings with new transcoder logic (#635)
* add LIBVA_DRIVER_NAME env var

* add vaapi device name

* add FFREPORT env var

* fixes
2022-02-15 08:44:49 -06:00
Jason Dove
c96b800b52 ffmpeg lib fixes (#633)
* try to fix vaapi inconsistencies

* log unexpected data

* vaapi fixes

* disable 444 test

* add qsv deinterlace filter; qsv fixes

* add videotoolbox acceleration
2022-02-14 22:01:26 -06:00
Jason Dove
c05882f4a6 fix docker builds 2022-02-14 14:36:35 -06:00
Jason Dove
5a442a06a0 start to use new ffmpeg library (#632)
* start to add ffmpeg library

* start to hook ffmpeg lib into main app

* improvements

* more progress

* make pipeline builder configurable

* more options

* move more logic down into ffmpeg lib

* ffmpeg lib desired state refactoring

* add software scaling and padding

* add loudness normalization and software deinterlace

* add metadata output options

* add setsar filter

* use built-in scaling logic

* fixes

* initial nvidia support

* nvidia improvements

* support hls mode

* print old arguments at debug level

* fix package reference

* start to add qsv support

* formatting

* fix tests

* add timeout to transcode tests

* show successful ffmpeg arguments

* add vaapi support

* add more software decoders

* add experimental transcoder option

* call existing ffmpeg process service for unimplemented features

* fix nvidia mpeg2video bug

* update changelog

* ignore some neglected unit tests
2022-02-14 14:34:00 -06:00
Jason Dove
640fed0a43 fix hls segmenter bug with unknown packet duration 2022-02-11 18:37:02 -06:00
Jason Dove
ab1f294c1f update changelog for release v0.4.1-alpha 2022-02-10 20:49:26 -06:00
Jason Dove
ea08453913 vaapi improvements (#629)
* fix interlaced video with vaapi

* downgrade imagesharp to fix blurhash generation

* fix ui crash loading collection editor
2022-02-10 20:19:59 -06:00
Jason Dove
87deaa6f3a nvidia improvements (#628) 2022-02-10 15:39:45 -06:00
Jason Dove
9d99c19ea4 fix playback with unknown pixel format (#627) 2022-02-10 08:37:25 -06:00
Jason Dove
49d14b05f6 update more dependencies (#626) 2022-02-09 11:08:49 -06:00
Jason Dove
a8ba9edf2b update dependencies (#624)
* update dependencies

* include refit xml serializer
2022-02-09 10:10:12 -06:00
Jason Dove
89811a1203 wait for one segment by default (#617) 2022-02-07 12:44:12 -06:00
Jason Dove
534e2c4512 add hls segmenter initial segment count (#616) 2022-02-07 12:18:58 -06:00
dependabot[bot]
c1e148633d Bump Blazored.LocalStorage from 4.1.5 to 4.2.0 (#614)
Bumps [Blazored.LocalStorage](https://github.com/Blazored/LocalStorage) from 4.1.5 to 4.2.0.
- [Release notes](https://github.com/Blazored/LocalStorage/releases)
- [Commits](https://github.com/Blazored/LocalStorage/compare/v4.1.5...v4.2.0)

---
updated-dependencies:
- dependency-name: Blazored.LocalStorage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-07 03:10:34 -06:00
Jason Dove
a9dff5eff7 properly flag local missing folders (#615) 2022-02-07 02:39:24 -06:00
Jason Dove
a2da043f4b try to fix mac permission issues 2022-02-06 17:45:28 -06:00
dependabot[bot]
252c185562 Bump MudBlazor from 6.0.5 to 6.0.6 (#609)
Bumps [MudBlazor](https://github.com/MudBlazor/MudBlazor) from 6.0.5 to 6.0.6.
- [Release notes](https://github.com/MudBlazor/MudBlazor/releases)
- [Changelog](https://github.com/MudBlazor/MudBlazor/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/MudBlazor/MudBlazor/compare/v6.0.5...v6.0.6)

---
updated-dependencies:
- dependency-name: MudBlazor
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-05 18:20:45 -06:00
Jason Dove
a47987a9d7 revert windows trimming (#613)
* Revert "disable trimming in docker"

This reverts commit 5937211bb8.

* Revert "try to reduce windows artifact size"

This reverts commit e32dbd0474.
2022-02-05 18:09:02 -06:00
Jason Dove
5937211bb8 disable trimming in docker 2022-02-05 13:41:47 -06:00
Jason Dove
e32dbd0474 try to reduce windows artifact size 2022-02-05 13:34:21 -06:00
Jason Dove
6bcc1ede2b try again 2022-02-05 11:25:14 -06:00
Jason Dove
6c9764a51e try a different method for downloading ffmpeg 2022-02-05 11:19:56 -06:00
Jason Dove
ff5438459c try to bundle ffmpeg with windows artifacts 2022-02-05 11:09:01 -06:00
Jason Dove
0c53a4509c show collection name in some error messages (#612) 2022-02-04 20:17:17 -06:00
Jason Dove
5fd315ead8 change framerate normalization method (#611) 2022-02-04 14:39:27 -06:00
Jason Dove
f02b0ac345 re-introduce framerate normalization (#610) 2022-02-04 12:57:40 -06:00
Jason Dove
fd83007296 try to fix watermark on vaapi 2022-02-01 17:50:47 -06:00
Jason Dove
70ca5bf050 fix bug with watermark and short content (#608) 2022-01-31 14:07:12 -06:00
Jason Dove
eed9f60273 fade in and fade out intermittent watermarks (#607)
* first pass at fading in/out overlay

* fix tests

* update changelog
2022-01-31 12:31:56 -06:00
Jason Dove
0e2e6cd52e build linux-arm64 artifacts 2022-01-31 01:07:03 -06:00
Jason Dove
c9b557f2e6 more xmltv category improvements (#606) 2022-01-30 17:58:45 -06:00
Jason Dove
cde869f3eb enable docker dependency scanning [no build] 2022-01-30 11:50:16 -06:00
Jason Dove
90d6a59d3f normalize smart quotes in search queries (#605) 2022-01-30 11:09:32 -06:00
Jason Dove
b972947747 xmltv category improvements (#604) 2022-01-30 10:56:53 -06:00
Jason Dove
17bc988b49 update changelog for release v0.4.0-alpha [no ci] 2022-01-29 18:22:07 -06:00
Jason Dove
749eea836b update install docs for tray apps (win, mac) [no build] 2022-01-29 18:18:58 -06:00
Jason Dove
37c52c4cb4 update docs and dependencies (#603) 2022-01-29 18:08:05 -06:00
Jason Dove
33ba58aa68 add windows launcher (#602) 2022-01-29 17:58:11 -06:00
Jason Dove
5f6043e593 index added date (#601) 2022-01-29 14:47:58 -06:00
Jason Dove
96e95a21fb update changelog [no ci] 2022-01-29 12:34:10 -06:00
Jason Dove
9168fd6bf2 write text file logs (#600) 2022-01-29 12:31:22 -06:00
Jason Dove
14413f62a7 properly sent content root on macos 2022-01-29 11:49:38 -06:00
Jason Dove
34c71a0c12 try to fix static resource loading 2022-01-29 10:20:03 -06:00
Jason Dove
a487e7fe15 use absolute paths in bundle script 2022-01-28 22:11:50 -06:00
Jason Dove
cd4ea42597 fix bundle script 2022-01-28 22:05:20 -06:00
Jason Dove
a3d42145f7 update macos submodule 2022-01-28 21:57:16 -06:00
Jason Dove
261cf5052a fetch submodules for mac build 2022-01-28 21:48:29 -06:00
Jason Dove
de9af2f0f6 first pass at native macos app 2022-01-28 21:41:26 -06:00
Jason Dove
8d4e18ed2f update mac app icon (#599) 2022-01-28 19:46:34 -06:00
Jason Dove
1ee01c1d78 fix hls timestamps (#598) 2022-01-27 23:51:18 -06:00
Jason Dove
7de50dd916 minor hls segmenter improvements (#593) 2022-01-26 20:12:29 -06:00
Jason Dove
744fd3beaa link file not found health check to trash (#592)
* update dependencies

* fix file not found health check
2022-01-26 08:37:40 -06:00
Jason Dove
861c95e1bd fix m3u mode override (#590) 2022-01-25 18:36:54 -06:00
Jason Dove
bb5b9f9be4 update changelog for release v0.3.8-alpha [no ci] 2022-01-23 21:46:00 -06:00
Jason Dove
135628441a re-add mac launcher script 2022-01-23 21:09:04 -06:00
Jason Dove
4aa7204984 fix ts mode with hdhr clients (#588) 2022-01-23 18:58:15 -06:00
Jason Dove
1af59a0337 don't use macos launcher script 2022-01-23 18:47:00 -06:00
Jason Dove
c4c97fcc8c customize mac dmg 2022-01-23 18:36:31 -06:00
Jason Dove
9c46e42792 fix gon variables 2022-01-23 14:21:13 -06:00
Jason Dove
efa803aab6 split mac artifacts job 2022-01-23 14:10:57 -06:00
Jason Dove
6ea02a2d77 use proper version number in ci artifacts [no docs] [no build] 2022-01-23 14:04:13 -06:00
Jason Dove
631f7d2d5e don't use reserved secret name 2022-01-23 13:54:37 -06:00
Jason Dove
e44a4cb2e1 properly pass secrets between workflows 2022-01-23 13:53:31 -06:00
Jason Dove
f4b95419a6 properly pass data between jobs 2022-01-23 13:46:47 -06:00
Jason Dove
1a5cf49563 refactor reusable docker workflow (#587)
* refactor reusable docker workflow

* refactor reusable artifacts workflow

* fix name

* try to fix

* fix
2022-01-23 13:42:01 -06:00
Jason Dove
efef0b0fee don't use single file for mac bundles 2022-01-22 17:29:35 -06:00
Jason Dove
ee7b8a71ab fix docker builds [no build] 2022-01-22 14:19:49 -06:00
Jason Dove
e7c9a51e96 macos app bundle (#585)
* test signed app bundle

* fix vars

* fix condition

* typo

* fix quoting

* use recursive signing script

* fix release cleanup

* restore proper ci action
2022-01-22 14:03:42 -06:00
Jason Dove
78a954f365 link to development builds in install docs 2022-01-21 20:55:38 -06:00
Jason Dove
355c0b7be9 try to fix deleting old assets 2022-01-21 20:29:50 -06:00
Jason Dove
3bcb2d36f9 another attempt at publishing artifacts 2022-01-21 20:21:26 -06:00
Jason Dove
b240de9d4a publish develop artifacts to stable release url 2022-01-21 18:44:25 -06:00
Jason Dove
f5001837cb properly separate build artifacts 2022-01-21 18:20:51 -06:00
Jason Dove
6ea916b1f0 fix fetch depth 2022-01-21 15:30:40 -06:00
Jason Dove
db6fd22215 try to fix build 2022-01-21 15:25:03 -06:00
Jason Dove
691842008d upload develop binaries for every merge to main (#584)
* upload develop binaries for every merge to main

* rename step
2022-01-21 15:18:08 -06:00
Jason Dove
685f78bef8 fix search results bug (#583) 2022-01-21 14:05:09 -06:00
Jason Dove
3ce267863b fix hls segmenter in some cultures (#582) 2022-01-21 10:42:33 -06:00
Jason Dove
e4231cb57d upgrade from ffmpeg 4.4 to 5.0 (#581) 2022-01-20 20:57:38 -06:00
Jason Dove
03946b13ca always use a single ffmpeg thread with realtime (#580) 2022-01-20 14:53:13 -06:00
Jason Dove
f1a81bf086 clarify library kind/media kind support (#579) [no docker] 2022-01-19 09:11:23 -06:00
Jason Dove
7a88374362 clarify flood scheduling calc [no ci] 2022-01-18 18:22:46 -06:00
Jason Dove
663a62431b properly fix startup paths (#576) 2022-01-17 16:31:22 -06:00
Jason Dove
1d4acc284d Update changelog for release v0.3.7-alpha [no ci] 2022-01-17 15:23:39 -06:00
Jason Dove
0440f7643b add videotoolbox acceleration (#575) 2022-01-17 15:05:23 -06:00
Jason Dove
0f4219f731 properly unlock libraries after failed scans (#574) 2022-01-14 13:03:15 -06:00
Jason Dove
cbe5d47611 fix trakt list sync when show does not contain a year (#572) 2022-01-12 21:09:26 -06:00
Jason Dove
afa52ccc89 add trash system for local libraries (#571)
* flag local movies as file not found

* show warning icon on cards

* unflag movie that is found during scan

* skip missing files when building playouts

* add state to search index

* add file not found health check

* link to search from file not found health check

* support flagging other media kinds as file not found

* continue to schedule missing items

* support episode files not found

* wip trash page

* fix trash url

* trash page is functional

* update changelog

* fix changelog merge
2022-01-12 20:27:53 -06:00
Jason Dove
7d1163c68f fix double-click startup on mac (#570) 2022-01-11 15:36:12 -06:00
Jason Dove
883492bd33 update changelog for release v0.3.6-alpha [no ci] 2022-01-10 19:51:01 -06:00
Jason Dove
a4eac4feea properly overwrite environment variables (#567) 2022-01-08 10:01:22 -06:00
Jason Dove
dab58f5840 fix tests 2022-01-07 19:18:31 -06:00
Jason Dove
176f136c23 fix some nvenc edge cases where only padding is needed for normalization (#565) 2022-01-07 18:53:28 -06:00
Jason Dove
816d77e15b update changelog [no ci] 2022-01-06 12:01:25 -06:00
Jason Dove
7c4d47a211 update changelog [no ci] 2022-01-06 10:31:18 -06:00
Jason Dove
d9d2cfa8be search index fixes (#559)
* add music video artist to search index

* properly index minutes field when adding from scan

* bump search index version
2022-01-06 10:28:53 -06:00
Jason Dove
8036e46966 update streaming mode docs (#558) [no docker] 2022-01-06 09:10:12 -06:00
Jason Dove
594ce437fb rework mpeg-ts mode (#557) 2022-01-05 21:27:28 -06:00
Jason Dove
004c43f895 update changelog for release v0.3.5-alpha [no ci] 2022-01-05 09:26:58 -06:00
Jason Dove
257384ea9b fix health checks (#556)
* update bundled ffmpeg version in health check

* recognize qsv acceleration on linux

* update changelog
2022-01-05 08:29:52 -06:00
Jason Dove
637f3a0c8b update docs [no docker] 2022-01-04 22:38:52 -06:00
Jason Dove
7346808059 update dependencies (#555) 2022-01-04 22:35:14 -06:00
Jason Dove
4210d97ee2 optimize setsar filter (#553) 2022-01-02 23:47:07 -06:00
Jason Dove
6a8ecd2532 use software decoding for mpeg4 with vaapi (#550) 2022-01-02 10:59:08 -06:00
Jason Dove
9b834f7cbe update changelog for release v0.3.4-alpha [no ci] 2021-12-21 09:46:43 -06:00
Jason Dove
7b73677bad allow ffmpeg reports on windows (#547)
* enable troubleshooting reports on windows

* update changelog

* tweak changelog
2021-12-21 09:27:49 -06:00
Jason Dove
85b2a46353 update dependencies (#546) 2021-12-21 08:52:51 -06:00
Jason Dove
6f40f2cbd6 fix songs docs [no docker] 2021-12-17 08:48:40 -06:00
Jason Dove
b62ee4dee9 add files from top-level folder (#541) 2021-12-14 14:27:12 -06:00
Jason Dove
a6e7f192cc add jellyfin path replacement tests [no ci] 2021-12-13 06:25:37 -06:00
Jason Dove
59a1a4a8dc update changelog for release v0.3.3-alpha [no ci] 2021-12-12 23:53:12 -06:00
Jason Dove
85a9afb51c update dependencies (#538) 2021-12-12 23:51:57 -06:00
Jason Dove
246b4d7591 properly sort channels in m3u (#537) 2021-12-10 20:22:52 -06:00
Jason Dove
ae2c6350e1 sync virtual shows and season from jellyfin (#536) 2021-12-10 14:41:47 -06:00
Jason Dove
ce228604e8 use select controls instead of autocomplete (#532)
* use select instead of autocomplete for playout editor

* use select instead of autocomplete for filler preset editor

* reset selected collection when changing collection type

* use select instead of autocomplete for multi collection editor

* more select

* more select controls
2021-12-06 12:49:48 -06:00
Jason Dove
3656e932d3 more song fixes (#529)
* use blurhash for default etv song backgrounds

* fix saving artwork blurhash

* fix song detail alignment

* rename song background files

* watermark path is always none here
2021-12-04 13:30:25 -06:00
Jason Dove
73887706ed update changelog for release v0.3.2-alpha [no ci] 2021-12-03 14:57:19 -06:00
Jason Dove
abc103308b optimize song artwork scanning (#527) 2021-12-03 13:40:55 -06:00
Jason Dove
3773bbec19 use blurhash for song backgrounds (#526)
* generate blurhash for all local artwork

* use blurhash song background if available

* only write blur hash to disk once

* use multiple blur hashes

* update changelog

* fix song detail outline

* reset song metadata (artwork)
2021-12-03 12:30:47 -06:00
Jason Dove
e223d6a43f remove unused cli project (#525) [no ci] 2021-12-02 09:01:47 -06:00
Jason Dove
8369111e31 update dependencies (#524) 2021-12-02 08:45:43 -06:00
Jason Dove
35ba2bab2c fix unicode song metadata on windows (#523)
* test setting utf8 encoding with ffprobe

* use utf8 encoding for console (logging) output

* use proper sink package

* reset song metadata on windows

* fix nfo processing with missing year

* update changelog
2021-12-01 13:43:09 -06:00
Jason Dove
094ed71ad0 fix docker builds with custom (local) nuget package (#520) 2021-11-30 20:49:02 -06:00
Jason Dove
89e24b2b78 use custom log database backend that is more portable (#519) 2021-11-30 20:34:14 -06:00
Jason Dove
848795af32 fix artwork upload on windows (#518)
* update changelog for release v0.3.1-alpha [no ci]

* fix artwork upload on windows
2021-11-30 12:23:24 -06:00
Jason Dove
56f94f489a fix filler playout crash (#517) 2021-11-30 10:38:42 -06:00
Jason Dove
475dc7660b fix artwork uploads (#516) 2021-11-29 14:34:18 -06:00
Jason Dove
db3dfbd446 disambiguate song search results (#515) 2021-11-27 21:37:55 -06:00
Jason Dove
b4c9cdbbfa use embedded song cover art (#514) 2021-11-27 21:08:18 -06:00
Jason Dove
7f84933c0b index song genres (#513)
* add song genres to search index

* reset all song genre metadata

* update changelog and docs
2021-11-27 18:08:55 -06:00
Jason Dove
1e35e9a5b0 use subtitles to display errors (#512)
* use subtitles to display errors

* fix margin calculation
2021-11-27 12:25:30 -06:00
Jason Dove
7edf6f5d13 song cleanup (#511)
* refactor song background logic

* move song video generation

* move subtitle generation

* build ASS subtitles

* randomize song detail layout

* update changelog
2021-11-27 11:15:53 -06:00
Jason Dove
919325033d use subtitles instead of drawtext for songs (#510) 2021-11-26 21:39:10 -06:00
Jason Dove
2cb5252320 fix song banding (#509)
* increase spacing in song details; uniformly darken to eliminate banding

* this isn't needed anymore
2021-11-26 15:20:41 -06:00
Jason Dove
015232fad6 song improvements (#508)
* fix song details margin and use dynamic font size

* sometimes use cover art color for song background
2021-11-26 13:23:28 -06:00
Jason Dove
af51b790b6 randomize cover art placement (#507) 2021-11-26 09:21:00 -06:00
Jason Dove
9195ef7878 song fixes (#506)
* fix song page links

* show song artist in playout detail

* show more song details in channel guide
2021-11-26 08:49:04 -06:00
Jason Dove
dfc4c7a284 update changelog for release v0.3.0-alpha [no ci] 2021-11-25 20:49:23 -06:00
Jason Dove
a6b15f68c9 randomize default backgrounds (#504)
* randomize default song backgrounds

* update docs
2021-11-25 20:19:26 -06:00
Jason Dove
0edfb71f8d limit disk use and keep cover art aspect ratio (#502)
* use temp file pool to limit disk use

* keep aspect ratio and crop when scaling cover art for blurred background

* fix typo
2021-11-25 18:47:22 -06:00
Jason Dove
21b90a1b6c fix songs with white backgrounds (#501) 2021-11-25 15:36:40 -06:00
Jason Dove
1582f5dd15 update changelog [no ci] 2021-11-25 13:37:33 -06:00
Jason Dove
fd3b72525d fix vaapi songs (#500) 2021-11-25 13:35:57 -06:00
Jason Dove
55d1871d94 re-enable hardware acceleration for songs (#499) 2021-11-25 13:04:13 -06:00
Jason Dove
a90eb2d4de optimize generated video (#498)
* use different framerate flags

* pre-generate song image and always use software encoders

* fix tests
2021-11-25 12:31:57 -06:00
Jason Dove
ed3f1b1dad generate song video (#497)
* use blurred cover art as song background

* use channel watermark when cover art is unavailable

* add drawtext to song filter

* cleanup

* force song cover art as png

* fix songs on windows and qsv
2021-11-25 06:22:38 -06:00
Jason Dove
8e08ff059f load embedded song metadata (#495)
* load embedded song metadata

* index song artist and song album

* reset all song metadata
2021-11-24 07:31:34 -06:00
Jason Dove
fb8c3a0453 disable autoscale when looping with vaapi or qsv (#494) 2021-11-23 13:25:23 -06:00
Jason Dove
e45fb67769 bug fixes (#493)
* don't align audio when playing songs

* fix grouping duration items in epg
2021-11-23 11:44:39 -06:00
Jason Dove
3a40d6ce77 fix local library locking when adding paths (#492) 2021-11-23 10:54:34 -06:00
Jason Dove
ac048b72ae add cover art watermark source (#491)
* add cover art watermark source

* update changelog
2021-11-23 10:02:36 -06:00
Jason Dove
852728c816 add songs libraries (#490)
* first pass at adding song libraries

* start handling optional video

* fix song playback

* fix song transitions

* add songs page to UI
2021-11-22 22:26:06 -06:00
Jason Dove
096f2d42e8 properly fix database upgrade (#489) 2021-11-22 17:56:29 -06:00
Jason Dove
1b29e252ff update changelog for release v0.2.5-alpha [no ci] 2021-11-21 07:24:20 -06:00
Jason Dove
a4dc9bfb31 Ignore local plex guids (#488)
* ignore local plex guids

* update dependencies
2021-11-21 06:25:56 -06:00
Jason Dove
184c21a91b optimize trakt matching (#487) 2021-11-21 06:13:28 -06:00
Jason Dove
6ea3191cf8 fix playout building (#486) 2021-11-20 22:36:15 -06:00
Jason Dove
d487bbca08 include other video title in channel guide (#483) 2021-11-16 08:46:07 -06:00
Jason Dove
05034b47e2 update changelog for release v0.2.4-alpha [no ci] 2021-11-13 12:54:41 -06:00
Jason Dove
b0c85b6478 use scale_cuda instead of scale_npp (#481) 2021-11-13 09:06:02 -06:00
Jason Dove
f1356563da fix ef shared table warnings (#480) 2021-11-10 18:03:28 -06:00
Jason Dove
c0aad028a8 more dotnet 6 updates (#479) 2021-11-09 13:09:57 -06:00
Jason Dove
dae06ec0ef upgrade to dotnet 6 (#475) 2021-11-09 07:44:34 -06:00
Jason Dove
72f452fd36 update dependencies (#474) 2021-11-09 06:16:26 -06:00
Jason Dove
aaf832c0b6 update changelog for release v0.2.3-alpha [no ci] 2021-11-03 13:53:06 -05:00
Jason Dove
08a18daf23 movie scanner should respect .etvignore files (#468) 2021-11-03 05:47:29 -05:00
Jason Dove
90c1c61a09 fix bug with flood playout mode (#467) 2021-11-02 21:47:46 -05:00
Jason Dove
053db71d44 fix decimal separator in ffmpeg apad filter syntax (#464) 2021-11-01 22:18:06 -05:00
Jason Dove
11f90f5d44 update changelog for release v0.2.2-alpha [no ci] 2021-10-30 17:49:30 -05:00
Jason Dove
bda4117655 allow per-episode folders in local show libraries (#462)
* allow per-episode folders in local show libraries

* fix subfolder etag generation
2021-10-30 12:54:07 -05:00
Jason Dove
3240703840 fix build 2021-10-30 12:24:27 -05:00
Jason Dove
53a7570ba3 fix epg for multiple playout mode (#461) 2021-10-30 12:16:39 -05:00
Jason Dove
0e789fd6d8 update dependencies and fix languageext deprecation warnings (#460) 2021-10-30 11:57:50 -05:00
Jason Dove
0136de700c add global and channel fallback filler (#459)
* configure channel and global fallback filler

* play random item from configured channel/global fallback filler as needed
2021-10-30 11:45:40 -05:00
Jason Dove
2ea0e64ac1 fix duration schedule item epg (#455) 2021-10-24 22:00:21 -05:00
Jason Dove
5993f23ec5 update changelog for release v0.2.1-alpha [no ci] 2021-10-24 19:46:33 -05:00
Jason Dove
417f35a834 fix saving dynamic start time (#453) 2021-10-24 18:18:22 -05:00
Jason Dove
a74547997d scheduling fixes (#451)
* scheduling fixes

* restore plex service

* restore plex service part 2
2021-10-24 06:49:35 -05:00
Jason Dove
a2f74dd284 update changelog for release v0.2.0-alpha [no ci] 2021-10-23 20:18:01 -05:00
Jason Dove
373daf9ce6 add basic filler docs [no docker] 2021-10-23 20:14:10 -05:00
Jason Dove
68693cffa0 use info log level for search index migration 2021-10-21 20:43:17 -05:00
Jason Dove
6d147de2f3 filler rework (#449)
* add chapter statistics and new filler options

* refactor playout builder

* more refactor prep for filler

* rewrite schedulers

* refactor collectionkey

* add tail filler kind

* migrate tail filler to filler preset

* optionally show filler

* fix playout detail row count

* remove duration tail filler options

* implement tail and fallback in flood scheduler

* implement tail and fallback in one scheduler

* implement tail and fallback in multiple scheduler

* implement looping fallback filler

* more duration tests

* start to add post-roll filler to flood

* rework playoutitem filler tagging

* rework scheduler logging

* calculate whether configured filler will fit

* implement pre-roll and post-roll duration and count filler

* improve duration filler calculation

* add minutes to search index

* update channel guide to work with new filler

* add mid-roll filler

* don't clone enumerators for filler calculations

* support pre-roll and post-roll pad filler

* implement mid-roll pad filler

* allow clearing filler selections in schedule editor

* fix tests

* filler config validation

* use consistent time zone for tests
2021-10-21 20:23:14 -05:00
Jason Dove
f4a63a1a1a fix deleting jellyfin and emby movies (#447)
* fix deleting jellyfin and emby movies

* revert jf service change
2021-10-19 14:33:48 -05:00
Jason Dove
bc9d17ca25 show path replacement logs by default (#445) 2021-10-19 06:32:00 -05:00
Jason Dove
42e13cbbaf fix generated streams with mpeg2video (#444) 2021-10-18 19:51:50 -05:00
Jason Dove
6cc61f3212 update changelog for release v0.1.5-alpha [no ci] 2021-10-18 17:55:34 -05:00
Jason Dove
4cf44616a8 Include music video thumbnail in epg (#443)
* include music video thumbnail in epg

* update changelog
2021-10-18 17:53:58 -05:00
Jason Dove
33aaadae68 multiple fixes to duration mode (#442) 2021-10-18 16:21:32 -05:00
Jason Dove
fe3f8e391e fix updating jellyfin and emby artwork (#440) 2021-10-17 05:36:00 -05:00
Jason Dove
1a68dd040a find working plex connection on startup (#438) 2021-10-16 11:55:54 -05:00
Jason Dove
67761c1a14 fix updating jellyfin and emby tv seasons (#437)
* fix updating jellyfin and emby tv seasons

* update changelog
2021-10-16 09:05:08 -05:00
Jason Dove
1802f9d797 fix database migration (#436) 2021-10-15 11:31:40 -05:00
Jason Dove
69354c9296 fix double scheduling (#435) 2021-10-15 09:14:53 -05:00
Jason Dove
0021e21b50 fix other video playback 2021-10-14 22:53:32 -05:00
Jason Dove
cdf7765059 update changelog for release v0.1.4-alpha [no ci] 2021-10-14 15:00:24 -05:00
Jason Dove
71658c448f update docs (#431) 2021-10-14 14:41:12 -05:00
Jason Dove
3ecdd741a5 add guide mode to schedule items (#430) 2021-10-14 13:24:54 -05:00
Jason Dove
0daeb844b9 add other videos library kind (#429) 2021-10-14 12:58:37 -05:00
Jason Dove
22da19845b add filler option to duration playout mode (#428)
* add duration tail options to schedule items editor

* add naive filler scheduling

* fix duration item length in xmltv

* show offline image for unfilled duration tail

* fix tests

* update changelog

* update dependencies
2021-10-13 21:15:16 -05:00
Jason Dove
3a6d9e9f39 update changelog for release v0.1.3-alpha [no ci] 2021-10-13 15:17:23 -05:00
Jason Dove
7ed4b8ae3c fix startup bug (#426) 2021-10-13 13:30:26 -05:00
Jason Dove
be7311e620 update changelog for release v0.1.2-alpha [no ci] 2021-10-12 19:00:30 -05:00
Jason Dove
03be372070 update changelog [no ci] 2021-10-12 18:57:02 -05:00
Jason Dove
d196308ee9 fix ffmpeg profile editing (#421) 2021-10-12 18:39:51 -05:00
Jason Dove
3d68b0f055 fix vaapi migration 2021-10-12 18:22:09 -05:00
Jason Dove
37e32f06ad update changelog [no ci] 2021-10-12 17:56:56 -05:00
Jason Dove
c43ca2837d support radeon vaapi acceleration (#420) 2021-10-12 17:51:55 -05:00
Jason Dove
992121f308 add more watermark locations (#419) 2021-10-12 07:19:52 -05:00
Jason Dove
04adbfeffa add hls segmenter settings to optimize performance (#418)
* add hls segmenter settings to optimize performance

* use consistent setting defaults
2021-10-12 06:31:11 -05:00
Jason Dove
1fc905c6ad upgrade vaapi to ffmpeg 4.4 (#417) 2021-10-11 22:26:32 -05:00
Jason Dove
4b5dff2159 ffnvcodec fixes (#416) 2021-10-11 22:14:43 -05:00
Jason Dove
2a5edf8214 ffmpeg 4.4 llvm nvidia fixes (#415) 2021-10-11 21:31:59 -05:00
Jason Dove
69912c8cae support ffmpeg 4.4 (#414)
* support ffmpeg 4.4

* update changelog
2021-10-11 20:16:15 -05:00
Jason Dove
fd3de2d82a nvidia 10 bit fixes (#413) 2021-10-11 16:00:35 -05:00
Jason Dove
6ba9404752 nvidia transcoding improvements (#412)
* nvidia transcoding fixes

* use yadif_cuda to deinterlace
2021-10-10 22:40:43 -05:00
Jason Dove
db080375c5 update changelog for release v0.1.1-alpha [no ci] 2021-10-10 12:54:46 -05:00
Jason Dove
9abc7ad8b7 try to fix tests on windows 2021-10-10 12:39:08 -05:00
Jason Dove
9e531a82d7 add some hls playlist filter tests (#411) 2021-10-10 12:33:02 -05:00
Jason Dove
d84bd2b948 upgrade nvidia docker image (#410) 2021-10-10 11:45:02 -05:00
Jason Dove
d7d3ec1235 add music video album to search index (#409)
* add music video album to search index

* update search docs
2021-10-10 10:28:35 -05:00
Jason Dove
742ac21ad7 update collection docs [no docker] 2021-10-10 07:53:29 -05:00
Jason Dove
819b55e21f increase max hls segments (#408) 2021-10-10 06:47:24 -05:00
Jason Dove
cf5718c288 rework hls segmenter (#407)
* rework hls segmenter to start more quickly

* don't use realtime encoding for hls until we're at least a minute ahead

* ugly but functional playlist filtering
2021-10-09 22:46:38 -05:00
Jason Dove
adc7982955 reduce initial hls segmenter delay (#406) 2021-10-09 10:26:57 -05:00
Jason Dove
67a6f554d0 rename v0.0.63-alpha v0.1.0-alpha [no ci] 2021-10-08 18:44:49 -05:00
Jason Dove
609df217ae update changelog for release 63 [no ci] 2021-10-08 18:39:03 -05:00
Jason Dove
d3086264c7 unraid doc fixes (#405) 2021-10-08 18:31:22 -05:00
Jason Dove
8cd9b23787 fix transcode folder preparation (#404) 2021-10-08 15:29:19 -05:00
dependabot[bot]
dc5c9e42ff Bump Serilog.Settings.Configuration from 3.2.0 to 3.3.0 (#403)
Bumps [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/serilog/serilog-settings-configuration/releases)
- [Changelog](https://github.com/serilog/serilog-settings-configuration/blob/dev/CHANGES.md)
- [Commits](https://github.com/serilog/serilog-settings-configuration/commits)

---
updated-dependencies:
- dependency-name: Serilog.Settings.Configuration
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-08 09:09:57 -05:00
Jason Dove
2dd267e4db fix xmltv generation with missing episode metadata (#402) 2021-10-07 22:31:46 -05:00
Jason Dove
b069a21473 allow hls segmenter to run before framerate is known (#401) 2021-10-07 22:07:54 -05:00
Jason Dove
6c8813ce22 add hls segmenter streaming mode (#400)
* hls segmenter wip

* log message

* close unused transcode sessions after 2 minutes

* use frame rate for 2s keyframes in hls segmenter

* add frame rate to media version

* fix segmenter framerate calculation

* automatically restart hls segmenter with next scheduled item

* cleanup

* update changelog

* decrease segmenter start delay
2021-10-07 21:42:29 -05:00
Jason Dove
b5de5e2b7f fix statistic updates (#399) 2021-10-07 19:56:41 -05:00
Jason Dove
4b7da4e468 speed up builds by using base images (#398) 2021-10-06 06:06:13 -05:00
Jason Dove
ae8e795228 vaapi downsample 10bit hevc 8bit h264 (#397)
* vaapi downsample 10bit hevc to 8bit h264

* update changelog
2021-10-05 22:16:57 -05:00
Jason Dove
334781485d use latest intel driver in vaapi docker images (#396)
* compile latest iHD driver

* cleanup to reduce image size

* update changelog
2021-10-05 20:24:26 -05:00
Jason Dove
27fefa1b38 update changelog for release 62 [no ci] 2021-10-05 15:28:45 -05:00
Jason Dove
fc3175591e use libx264 for all errors (#395) 2021-10-05 15:13:35 -05:00
Jason Dove
3363d2c9d7 update plex paths when they are changed (#394) 2021-10-05 06:27:05 -05:00
Jason Dove
1d5217fa84 support imdb ids from plex (#393) 2021-10-05 06:01:53 -05:00
Jason Dove
904cdb8780 vaapi improvements (#392)
* add transcoding tests

* dont use full paths for transcoding tests

* add error details

* use 1-second videos for transcoding tests

* vaapi fixes

* include format in scale_vaapi

* more vaapi fixes

* unsupported errors

* fix unsupported checks

* maybe not failure?

* fix formatting

* ignore nvdec warnings

* update changelog

* fix tests
2021-10-02 13:42:44 -05:00
Jason Dove
85fee64565 update changelog [no ci] 2021-10-01 13:44:04 -05:00
Jason Dove
13cfb9728f include season zero episode-num in xmltv (#391) 2021-10-01 13:42:46 -05:00
Jason Dove
60b82876ea mudblazor updates (#390) 2021-09-30 17:40:26 -05:00
Jason Dove
a99249c375 revert nvenc changes (#389) 2021-09-30 17:35:44 -05:00
Jason Dove
36e6ef4c18 update changelog for release 60 2021-09-25 15:12:47 -05:00
Jason Dove
21e53532c1 trakt season bug fixes (#386) 2021-09-25 14:55:42 -05:00
Jason Dove
a864d53327 add seasons to search index (#385)
* update trakt list items when re-adding existing list

* add seasons to search index
2021-09-25 14:01:35 -05:00
Jason Dove
e6446f9983 better trakt lists (#384)
* better trakt list support

* update dependencies

* revert unneeded brackets
2021-09-25 09:12:25 -05:00
Jason Dove
ad40213f90 fix synchronizing trakt lists that contain unreleased movies (#382) 2021-09-21 21:14:27 -05:00
Jason Dove
45c6d20fd0 sync trakt list to collection (#381)
* sync trakt list to collection

* move trakt client id
2021-09-20 18:46:03 -05:00
Jason Dove
5439db89a7 nvidia fixes (#380)
* nvidia fixes

* fix tests
2021-09-19 21:39:36 -05:00
Jason Dove
a39231bb5a fix local episode metadata update (#379) 2021-09-19 20:57:12 -05:00
Jason Dove
4c8584b517 try to fix develop versioning 2021-09-18 18:08:19 -05:00
Jason Dove
ca8bcacbd3 update changelog for release 59 [no ci] 2021-09-18 14:35:01 -05:00
Jason Dove
f27286d1dd properly disable transcoding when unchecked in mpeg-ts mode (#378)
* properly disable transcoding in MPEG-TS mode

* update changelog
2021-09-18 14:21:49 -05:00
Jason Dove
23870b75f7 update changelog [no ci] 2021-09-18 14:02:49 -05:00
Jason Dove
7f5a91c643 include libva-x11-2 in vaapi docker image 2021-09-18 13:25:01 -05:00
Jason Dove
f1f50e883c add vaapi driver setting and health check (#377)
* add vaapi driver option

* add vaapi driver setting and health check
2021-09-18 13:00:36 -05:00
Jason Dove
7506f49f5b remove codeql [no docker] 2021-09-18 11:30:16 -05:00
Jason Dove
944f1e4307 add scheduled playout rebuild (#376)
* configure scheduled playout rebuild

* implement scheduled playout rebuild

* remove variable
2021-09-18 11:23:58 -05:00
Jason Dove
f7de9ac5ea include intel-media-va-driver-non-free in vaapi image (#375)
* include intel-media-va-driver-non-free in vaapi image

* tweak changelog
2021-09-17 21:13:27 -05:00
Jason Dove
1eb51ad2f4 add some health checks to home page (#374) 2021-09-17 17:59:59 -05:00
Jason Dove
c3e0aaf0b7 missing metadata fixes (#373) 2021-09-17 08:53:41 -05:00
Jason Dove
b9912b47df update changelog for release 58 [no ci] 2021-09-15 21:53:34 -05:00
Jason Dove
55fb2624e7 add multi-part grouping tooltip (#371) 2021-09-15 21:18:41 -05:00
Jason Dove
8ced20dc39 dont offset collections during shuffle in order (#370) 2021-09-15 20:54:32 -05:00
Jason Dove
e718cb0faf fix building playouts in timezones with positive offsets (#368) 2021-09-15 09:07:35 -05:00
Jason Dove
e218ff9a6d fix watermark when no video filters are required (#367) 2021-09-15 05:12:23 -05:00
Jason Dove
c2a49cbaea update dependencies (#365) 2021-09-14 18:10:59 -05:00
Jason Dove
17e74f7314 add more release date search options (#362) 2021-09-12 18:32:38 -05:00
Long-Man
2032bb4777 Update search.md (#361)
Add release_date and released_nointhelast to music video search
2021-09-12 12:18:07 -05:00
Jason Dove
7877ec641e update changelog for release 57 [no ci] 2021-09-11 10:37:23 -05:00
Jason Dove
767a9779bb more kodi artwork fixes (#360) 2021-09-11 10:08:33 -05:00
Jason Dove
bb9127e546 fix artwork in kodi (#359) 2021-09-11 09:45:09 -05:00
Jason Dove
c932577cb8 allow adding smart collections to multi collections (#358) 2021-09-11 09:29:20 -05:00
Jason Dove
ad2685fb2e add released_inthelast queries (#357) 2021-09-10 21:11:14 -05:00
Jason Dove
96bc2c28f2 update changelog for release 56 [no ci] 2021-09-10 13:59:14 -05:00
Jason Dove
a076b3eb30 add shuffle-in-order support to all collections (#356) 2021-09-10 13:33:04 -05:00
Jason Dove
fc360602ad add smart collections (#355)
* start to add smart collections

* add smart collection table; delete smart collection

* overwrite smart collections

* support scheduling smart collections

* update changelog
2021-09-10 11:58:24 -05:00
Jason Dove
d8b4d00a73 clarify changelog [no ci] 2021-09-09 08:22:46 -05:00
Jason Dove
0638ac8a5e more missing metadata fixes (#354)
* more missing metadata fixes

* update mudblazor
2021-09-09 06:38:46 -05:00
Jason Dove
f1f09bd4cb fix sorting episodes without metadata (#353) 2021-09-08 22:12:47 -05:00
Jason Dove
f6680f29e7 try to fix doc formatting [no docker] 2021-09-07 13:36:25 -05:00
Jason Dove
1c0413452b fix m3u xmltv mapping 2021-09-07 06:34:17 -05:00
Jason Dove
77308a9ac5 generate valid xmltv (#351) 2021-09-07 06:12:13 -05:00
Jason Dove
3ea8193bb3 update changelog for release 55 [no ci] 2021-09-03 08:56:25 -05:00
Jason Dove
8ad8680027 update dependencies; fix unnecessary table scrolling (#347) 2021-09-03 06:22:50 -05:00
Jason Dove
640044814c ignore dot-underscore files (#346) 2021-09-03 06:22:33 -05:00
Jason Dove
18b5313a53 update docs [no docker] 2021-08-22 20:17:39 -05:00
Jason Dove
8417c3f6cd update changelog for release 54 [no ci] 2021-08-21 13:19:13 -05:00
Jason Dove
32fdb414fa add "shuffle in order" playback order for multi-collections (#338)
* add "shuffle in order" option for multi-collections

* use balanced shuffle instead of random
2021-08-21 12:47:22 -05:00
Jason Dove
d3fc820aef update dependencies (#336)
* update dependencies

* fix fluent assertions
2021-08-21 06:23:43 -05:00
Jason Dove
9d07627781 fix ffprobe parsing in some cultures (#337) 2021-08-21 05:57:39 -05:00
Jason Dove
d3c8914758 update dependencies (#331) 2021-08-14 07:20:09 -05:00
Jason Dove
3d7ec59088 update changelog for release 53 [no ci] 2021-08-01 12:37:10 -05:00
Jason Dove
d78976f80a update changelog [no ci] 2021-08-01 12:22:32 -05:00
Jason Dove
0f5fee99c6 always proxy jellyfin and emby artwork (#323) 2021-08-01 12:19:50 -05:00
dependabot[bot]
d5bfd1a254 Bump MudBlazor from 5.0.15 to 5.1.0 (#321)
Bumps [MudBlazor](https://github.com/Garderoben/MudBlazor) from 5.0.15 to 5.1.0.
- [Release notes](https://github.com/Garderoben/MudBlazor/releases)
- [Changelog](https://github.com/Garderoben/MudBlazor/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/Garderoben/MudBlazor/compare/v5.0.15...v5.1.0)

---
updated-dependencies:
- dependency-name: MudBlazor
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-28 05:37:38 -05:00
Jason Dove
cd2558e3e6 fix jellyfin and emby links (#320) 2021-07-27 18:26:12 -05:00
Jason Dove
c39654ca40 update changelog for release 52 [no ci] 2021-07-22 11:26:05 -05:00
Jason Dove
f17151bd20 fix multi-collection bug (#318) 2021-07-22 11:12:14 -05:00
Jason Dove
6aeaf65a13 update docs [no docker] 2021-07-20 20:13:21 -05:00
Jason Dove
9fbe950e6e support multiple local libraries (#317)
* allow multiple local libraries

* add "move library path" function
2021-07-20 07:47:12 -05:00
Jason Dove
c9baff2cd5 add codeql workflow [no docker] 2021-07-18 07:36:41 -05:00
Jason Dove
447829385f update changelog for release 51 [no docker] 2021-07-18 07:11:11 -05:00
Jason Dove
a94d831866 add playout days to build setting (#316) 2021-07-18 06:10:54 -05:00
Jason Dove
632753ea93 add multi collections (#315)
* start to add multi-collections

* create multi collection with no items

* edit multi collections

* fix plex credentials threading issue

* add playback order to multi collection items

* group episodes outside of shuffled enumerator

* move playback order onto each schedule item

* fix multi collection grouping

* update changelog
2021-07-17 21:45:41 -05:00
Jason Dove
4000c6bc0a update changelog [no docker] 2021-07-15 04:40:39 -05:00
Jason Dove
1521469b2f add sqlite linux-arm artifacts (#314) 2021-07-15 04:39:04 -05:00
Jason Dove
d6272c54a0 fix nuget references 2021-07-14 09:09:26 -05:00
dependabot[bot]
d5039dc4fc Bump Serilog.Sinks.Console from 3.1.1 to 4.0.0 (#303)
Bumps [Serilog.Sinks.Console](https://github.com/serilog/serilog-sinks-console) from 3.1.1 to 4.0.0.
- [Release notes](https://github.com/serilog/serilog-sinks-console/releases)
- [Commits](https://github.com/serilog/serilog-sinks-console/compare/v3.1.1...v4.0.0)

---
updated-dependencies:
- dependency-name: Serilog.Sinks.Console
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-14 07:10:06 -05:00
Jason Dove
eba50523a9 fix release notes on homepage with alpha suffix (#312) 2021-07-14 05:35:11 -05:00
Jason Dove
1c1c1e7812 update changelog for release 50 [no docker] 2021-07-13 20:29:00 -05:00
Jason Dove
5f802c7484 include linux-arm release artifacts [no docker] (#306) 2021-07-13 20:21:37 -05:00
Jason Dove
7a06ac71e2 fix movie fallback metadata titles (#305) 2021-07-13 20:15:52 -05:00
Jason Dove
3b9b8796b9 fix movie fallback metadata (#302) 2021-07-12 07:44:48 -05:00
dependabot[bot]
a72d91507e Bump FluentValidation.AspNetCore from 10.2.3 to 10.3.0 (#301)
Bumps [FluentValidation.AspNetCore](https://github.com/JeremySkinner/fluentvalidation) from 10.2.3 to 10.3.0.
- [Release notes](https://github.com/JeremySkinner/fluentvalidation/releases)
- [Changelog](https://github.com/FluentValidation/FluentValidation/blob/main/Changelog.txt)
- [Commits](https://github.com/JeremySkinner/fluentvalidation/compare/10.2.3...10.3.0)

---
updated-dependencies:
- dependency-name: FluentValidation.AspNetCore
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-12 06:05:54 -05:00
dependabot[bot]
45bfbfc179 Bump FluentValidation from 10.2.3 to 10.3.0 (#300)
Bumps [FluentValidation](https://github.com/JeremySkinner/fluentvalidation) from 10.2.3 to 10.3.0.
- [Release notes](https://github.com/JeremySkinner/fluentvalidation/releases)
- [Changelog](https://github.com/FluentValidation/FluentValidation/blob/main/Changelog.txt)
- [Commits](https://github.com/JeremySkinner/fluentvalidation/compare/10.2.3...10.3.0)

---
updated-dependencies:
- dependency-name: FluentValidation
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-12 05:51:29 -05:00
Jason Dove
7d24701a82 update changelog for release 49 [no docker] 2021-07-11 18:00:50 -05:00
Jason Dove
286580d5aa more sorting fixes (#299) 2021-07-11 14:40:31 -05:00
Jason Dove
d9457c01e5 add special case to multiple playout mode (#298) 2021-07-11 14:25:42 -05:00
Jason Dove
22cf759a29 sorting fixes (#297)
* sorting fixes

* use natural sort for add to schedule dialog
2021-07-11 13:20:42 -05:00
Jason Dove
0733a3d8d7 fix loading playout anchors (#296) 2021-07-11 10:47:25 -05:00
Jason Dove
5f28707cce include audio language metadata in all streaming modes (#295)
* include audio language metadata in all streaming modes

* cleanup
2021-07-10 06:16:46 -05:00
Jason Dove
45f1c6b22a properly flood with fixed start time (#294) 2021-07-09 14:16:24 -05:00
dependabot[bot]
3bed81aee9 Bump MudBlazor from 5.0.14 to 5.0.15 (#292)
Bumps [MudBlazor](https://github.com/Garderoben/MudBlazor) from 5.0.14 to 5.0.15.
- [Release notes](https://github.com/Garderoben/MudBlazor/releases)
- [Changelog](https://github.com/Garderoben/MudBlazor/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/Garderoben/MudBlazor/compare/v5.0.14...v5.0.15)

---
updated-dependencies:
- dependency-name: MudBlazor
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-09 13:49:52 -05:00
Jason Dove
f2eda3033c update dependencies (#289) 2021-06-24 06:35:50 -05:00
dependabot[bot]
8ce989c3c9 Bump Microsoft.VisualStudio.Threading.Analyzers from 16.9.60 to 16.10.56 (#282)
Bumps [Microsoft.VisualStudio.Threading.Analyzers](https://github.com/microsoft/vs-threading) from 16.9.60 to 16.10.56.
- [Release notes](https://github.com/microsoft/vs-threading/releases)
- [Commits](https://github.com/microsoft/vs-threading/compare/v16.9.60...v16.10.56)

---
updated-dependencies:
- dependency-name: Microsoft.VisualStudio.Threading.Analyzers
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 13:06:41 -05:00
dependabot[bot]
b5ba0dff27 Bump Blazored.LocalStorage from 4.1.1 to 4.1.2 (#286)
Bumps [Blazored.LocalStorage](https://github.com/Blazored/LocalStorage) from 4.1.1 to 4.1.2.
- [Release notes](https://github.com/Blazored/LocalStorage/releases)
- [Commits](https://github.com/Blazored/LocalStorage/compare/v4.1.1...v4.1.2)

---
updated-dependencies:
- dependency-name: Blazored.LocalStorage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 13:06:30 -05:00
dependabot[bot]
de3e2ea754 Bump NUnit3TestAdapter from 3.17.0 to 4.0.0 (#283)
Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 3.17.0 to 4.0.0.
- [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases)
- [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V3.17...V4.0.0)

---
updated-dependencies:
- dependency-name: NUnit3TestAdapter
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 05:44:05 -05:00
dependabot[bot]
2ac840b4bd Bump Markdig from 0.24.0 to 0.25.0 (#284)
Bumps [Markdig](https://github.com/xoofx/markdig) from 0.24.0 to 0.25.0.
- [Release notes](https://github.com/xoofx/markdig/releases)
- [Changelog](https://github.com/xoofx/markdig/blob/master/changelog.md)
- [Commits](https://github.com/xoofx/markdig/compare/0.24.0...0.25.0)

---
updated-dependencies:
- dependency-name: Markdig
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 05:43:53 -05:00
dependabot[bot]
c8ccb5b0a0 Bump Microsoft.NET.Test.Sdk from 16.9.4 to 16.10.0 (#285)
Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.9.4 to 16.10.0.
- [Release notes](https://github.com/microsoft/vstest/releases)
- [Commits](https://github.com/microsoft/vstest/compare/v16.9.4...v16.10.0)

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-23 05:43:42 -05:00
Jason Dove
23c4fcf42c update changelog for release 48 [no docker] 2021-06-22 20:05:43 -05:00
Jason Dove
e2f3e86fd6 fix adding jellyfin emby seasons episodes (#281)
* fix adding new seasons and episodes with emby and jellyfin

* update changelog

* update dependencies
2021-06-22 18:55:44 -05:00
dependabot[bot]
fd9f4a8f4e Bump MudBlazor from 5.0.10 to 5.0.14 (#278)
Bumps [MudBlazor](https://github.com/Garderoben/MudBlazor) from 5.0.10 to 5.0.14.
- [Release notes](https://github.com/Garderoben/MudBlazor/releases)
- [Changelog](https://github.com/Garderoben/MudBlazor/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/Garderoben/MudBlazor/compare/v5.0.10...v5.0.14)

---
updated-dependencies:
- dependency-name: MudBlazor
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-22 06:28:01 -05:00
dependabot[bot]
d5a0951a9b Bump FluentValidation from 10.1.0 to 10.2.3 (#275)
Bumps [FluentValidation](https://github.com/JeremySkinner/fluentvalidation) from 10.1.0 to 10.2.3.
- [Release notes](https://github.com/JeremySkinner/fluentvalidation/releases)
- [Changelog](https://github.com/FluentValidation/FluentValidation/blob/main/Changelog.txt)
- [Commits](https://github.com/JeremySkinner/fluentvalidation/compare/10.1.0...10.2.3)

---
updated-dependencies:
- dependency-name: FluentValidation
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-22 06:21:28 -05:00
Jason Dove
56d9724efd remove BOM from dependabot.yml [no docker] 2021-06-22 06:08:04 -05:00
Jason Dove
f91b5ab3b5 assign dependabot prs [no docker] 2021-06-22 06:06:20 -05:00
Jason Dove
4b8e81ff06 add dependabot config [no docker] 2021-06-22 06:00:54 -05:00
Jason Dove
1a7e6dda54 support 10-bit content with nvidia acceleration (#273)
* use ffprobe for plex statistics

* emby and jellyfin respect library refresh interval

* support 10-bit content with nvidia acceleration
2021-06-19 21:25:44 -05:00
Jason Dove
9fc6cdd0b7 update changelog for release 47 [no docker] 2021-06-15 16:25:01 -05:00
Jason Dove
cebab33d79 table improvements (#272)
* log viewer improvements

* playout detail table improvements

* schedule items table improvements

* remove schedule items pager
2021-06-15 16:10:24 -05:00
Jason Dove
b580125e86 fix searching when queries include non-ascii characters (#271) 2021-06-15 10:33:02 -05:00
Jason Dove
b38ba14c40 fix languages that have multiple codes (#270) 2021-06-15 06:12:40 -05:00
Jason Dove
c10bc6b184 fix blazor error font color (#269)
* fix blazor error font color

* changelog
2021-06-14 20:32:20 -05:00
Jason Dove
a75737a032 clear playout detail on delete (#266) 2021-06-14 18:44:10 -05:00
Jason Dove
57aa14b764 fix adding channel with no watermark (#265) 2021-06-14 18:31:50 -05:00
Jason Dove
6e6d5a133f update changelog for release 46 [no docker] 2021-06-14 14:03:10 -05:00
Jason Dove
b4ba37f778 reset watermarks (#263) 2021-06-14 13:42:13 -05:00
Jason Dove
275f82fcc9 save schedules and playouts table page sizes (#262) 2021-06-14 13:11:49 -05:00
Jason Dove
72d967946d rework watermarks (#261)
* rework watermarks to be separate from channels

* update changelog
2021-06-14 10:29:58 -05:00
Jason Dove
a0740de972 add global and channel watermark overrides (#260)
* add global watermark setting

* add channel watermark override

* update changelog
2021-06-13 21:45:52 -05:00
Jason Dove
e69569ea46 fix docker builds 2021-06-13 20:55:40 -05:00
Jason Dove
679feb6d21 add watermark opacity (#259) 2021-06-13 20:45:23 -05:00
Jason Dove
0fb5bfde58 refactor dbcontext lifetime (#258)
* refactor create playout handler

* refactor get all playouts handler

* refactor delete playout handler

* remove dead code

* ignore unnamed artists for collections

* more repository cleanup

* more schedule items refactoring

* more playout refactoring

* refactor playout builder

* refactor ffmpeg profiles

* more ffmpeg profile refactoring

* rework resolutions

* refactor media collections

* refactor config elements

* update changelog

* more cleanup
2021-06-13 20:19:10 -05:00
Jason Dove
4172074ac4 update changelog for release 45 [no docker] 2021-06-12 11:46:07 -05:00
Jason Dove
e9889cefd6 skip empty content rating (#257) 2021-06-12 11:31:32 -05:00
Jason Dove
fc59c9c284 include all content ratings in xmltv (#256) 2021-06-12 11:14:27 -05:00
Jason Dove
0750a0712f allow animated channel watermarks (#255) 2021-06-12 06:16:52 -05:00
Jason Dove
0365d4c8f8 add channel watermark (#254)
* wip

* wip

* implement watermark settings

* code cleanup

* update changelog
2021-06-11 21:42:06 -05:00
Jason Dove
5b36252dd0 remove framerate normalization (#253) 2021-06-11 18:04:16 -05:00
Jason Dove
7d852bc960 add hls hybrid mode (#252)
* fix serving channels.m3u with missing content ratings

* add hls hybrid mode
2021-06-10 20:42:58 -05:00
Jason Dove
cdf10b0535 changelog for 44 again [no docker] 2021-06-09 18:41:11 -05:00
Jason Dove
f0b429efb5 update changelog for release 44 [no docker] 2021-06-09 18:39:52 -05:00
Jason Dove
da5148affd quickly skip missing files during plex library scan (#251) 2021-06-07 20:34:24 -05:00
Jason Dove
cec5a09839 add us content ratings to xmltv (#250) 2021-06-07 18:58:38 -05:00
Jason Dove
e20f9be702 exclude strm files from jellyfin scanners (#249)
* exclude strm files from jellyfin scanners

* update changelog
2021-06-07 07:41:59 -05:00
Jason Dove
3bc3faa7c4 artist schedule doc update [no docker] 2021-06-06 20:25:39 -05:00
Jason Dove
db24ba84f7 add artists directly to schedules (#248) 2021-06-06 20:12:17 -05:00
Jason Dove
8346a02747 ignore unsupported plex guids (#246) 2021-06-05 15:53:38 -05:00
Jason Dove
c3b33c184f fix changelog [no docker] 2021-06-05 13:39:29 -05:00
Jason Dove
6bec9c5f07 update docs for 0.0.43-prealpha [no docker] 2021-06-05 13:36:08 -05:00
Jason Dove
0ef03d66f3 improve hls direct compatibility with channels dvr (#245)
* rename HttpLiveStreaming to HttpLiveStreamingDirect

* improve hls direct compatibility with channels dvr

* code cleanup
2021-06-05 13:15:39 -05:00
Jason Dove
10c422a3eb save channels table page size (#244)
* save channel table page size

* update changelog
2021-06-05 10:15:34 -05:00
Jason Dove
6c867d0d51 support multi-episode files from plex (#243)
* minor fallback metadata bug fixes

* support multi-episode files from plex
2021-06-04 15:06:19 -05:00
Jason Dove
ed0796ad58 force scan local season folders to pick up multi-episode files (#242) 2021-06-04 11:42:03 -05:00
Jason Dove
49109ac121 fix missing season metadata (#241) 2021-06-04 10:37:56 -05:00
Jason Dove
3e3bbcf38e support multi-episode files in local libraries (#240)
* add unused episode nfo reader

* move episode number from episode to episode metadata

* first pass at loading multi-episode metadata from nfo files

* fix episode scanning

* local multi-part episode fixes

* code cleanup
2021-06-04 06:00:35 -05:00
Jason Dove
ce9ef72799 support (part #) names for multi-episode grouping (#238) 2021-06-01 07:15:17 -05:00
Jason Dove
f8631a1f12 release 0.0.42-prealpha 2021-05-31 13:08:55 -05:00
Jason Dove
c70f153241 keep crossover episodes together (#237) 2021-05-31 11:58:56 -05:00
Jason Dove
eee10dee22 skip zero duration items when building playouts (#236) 2021-05-31 07:31:15 -05:00
Jason Dove
9f575dbd94 fix stuck playout builds (#235)
* fix stuck playout builds

* code cleanup
2021-05-31 06:38:54 -05:00
Jason Dove
539285d81e update docs for 0.0.41-prealpha release [no docker] 2021-05-30 18:01:13 -05:00
Jason Dove
f8c986472a more grouping fixes (#234)
* more grouping fixes

* update changelog
2021-05-30 11:54:02 -05:00
Jason Dove
442d73150e link to full changelog on home page [no docker] 2021-05-30 10:47:33 -05:00
Jason Dove
d6cee14143 add changelog (#232) 2021-05-30 09:40:59 -05:00
Jason Dove
c20c0b231e fix updating local movies (#231) 2021-05-30 06:32:26 -05:00
Jason Dove
e506dd38a8 merge latest develop (#230)
* sync guids/provider ids (#227)

* sync guids from plex

* cleanup

* sync local guids

* sync jellyfin and emby guids

* add episodes to search index (#228)

* sync episode directors and writers

* display episode writers and directors

* remove missing episodes from search index

* show episodes in search results

* fix emby and jellyfin episode updates

* fix updating plex episodes

* don't delete channel logos on startup

* add episodes page; fix adding episodes to collection

* cleanup

* multi-part episode grouping fixes (#229)
2021-05-30 06:08:33 -05:00
Jason Dove
bbd8bc6c7e add button to refresh list of libraries (#226)
* add button to refresh list of libraries

* code cleanup
2021-05-28 15:28:45 -05:00
Jason Dove
e841c9c53b fix missing artwork (#225) 2021-05-28 09:34:47 -05:00
Jason Dove
4c78f41c5a fix incorrect search items count (#224) 2021-05-28 06:13:40 -05:00
Jason Dove
95cceb95b9 regularly delete orphaned artwork from db (#223) 2021-05-28 05:38:58 -05:00
Jason Dove
58d6f81d2e recursively retrieve jellyfin and emby items (#221) 2021-05-27 21:01:25 -05:00
Jason Dove
fe5cedfcdc disable ffmpeg reports on windows (#220)
* disable ffmpeg reports for windows

* code cleanup
2021-05-27 16:36:02 -05:00
Jason Dove
0bbed69e85 add movie directors and writers (#219) 2021-05-27 09:24:47 -05:00
Jason Dove
68123a2f9c add content rating (#218)
* add new columns

* store local content ratings

* display and search content ratings

* add content_rating to search docs

* sync content rating from jellyfin, emby, plex

* force sync content rating for all libraries

* code cleanup
2021-05-27 06:41:24 -05:00
Jason Dove
6504ca10a8 cache local artwork on disk (#217) 2021-05-26 19:49:08 -05:00
Jason Dove
84770ed250 use artwork for schedule items with custom title when all media items are from same show (#216) 2021-05-26 15:34:54 -05:00
Jason Dove
466d33f808 sync tv show thumb art (#214)
* sync thumb art from local, jellyfin, emby

* code cleanup
2021-05-26 12:46:23 -05:00
Jason Dove
8e81d5f197 fix add to schedule dialog (#213) 2021-05-26 08:43:11 -05:00
Jason Dove
da43e6f7cf embed debug symbols (#212) 2021-05-26 07:26:14 -05:00
Jason Dove
c9905d0542 fix resources (offline background and font) (#211) 2021-05-25 15:42:03 -05:00
Jason Dove
c9e20e28df proxy jellyfin and emby artwork for xmltv (#210)
* fix xmltv artwork for jf and emby

* proxy jellyfin and emby artwork for xmltv
2021-05-25 15:15:15 -05:00
Jason Dove
f9427cac99 use multiple docker tags again (#209)
* Revert "disable framerate normalization (#208)"

This reverts commit 141a34933d.

* Revert "use linuxserver base docker image (#207)"

This reverts commit 0962a1429a.

* fix playback that only uses fps filter

* nvidia needs privileged
2021-05-25 05:13:51 -05:00
Jason Dove
141a34933d disable framerate normalization (#208)
* disable framerate normalization

* fix test
2021-05-24 21:47:26 -05:00
Jason Dove
0962a1429a use linuxserver base docker image (#207)
* use one base docker image

* remove nvidia and vaapi tags

* fix playback that only uses fps filter
2021-05-24 21:12:55 -05:00
Jason Dove
f8b45ed9db fix unc path replacements from jellyfin and emby (#205)
* fix UNC path replacements from non-windows JF and Emby servers

* use emby path replacements for playback
2021-05-24 09:03:39 -05:00
Jason Dove
266bfbad23 add link to release notes (#203) 2021-05-23 10:02:26 -05:00
Jason Dove
60a9640009 use ffmpeg 4.3 in docker (#202)
* Revert "fix ffmpeg 4.4 compatibility"

This reverts commit 1ca0df038c.

* use ffmpeg 4.3 in docker
2021-05-23 09:55:40 -05:00
Jason Dove
9291a6b6ed add emby docs 2021-05-23 03:50:00 -05:00
Jason Dove
9afec19888 cleanup 2021-05-23 03:27:20 -05:00
Jason Dove
50529ee6ad add emby media source (#201)
* properly scope jellyfin disconnect

* add emby entities

* add emby media source page

* add emby media source editor

* sync emby libraries

* enable emby library sync toggle

* add emby path replacements editor

* add emby movie synchronization

* fix emby artwork

* sync emby television

* code cleanup

* add jellyfin/emby address placeholder

* tweak jellyfin/emby address form
2021-05-23 03:05:23 -05:00
Jason Dove
0b105bf6e1 fix schedule item duration under one hour (#200) 2021-05-22 07:45:44 -05:00
Jason Dove
5356f7f293 update dependencies 2021-05-22 05:59:30 -05:00
Jason Dove
1d35efa429 fix jellyfin artwork (#198) 2021-05-21 21:21:32 -05:00
Jason Dove
04da4b2964 single-file app publishing (#197)
* attempt to fix single file app publishing

* update release workflow
2021-05-21 15:17:24 -05:00
Jason Dove
0799fe25d1 optimize local library scanning by using etags (#196)
* use etags to optimize local movie scanner

* use etags to optimize local television scanner

* use etags to optimize local music video scanner

* code cleanup
2021-05-21 06:18:07 -05:00
Jason Dove
c0b5ecd388 custom binding and port number (#195)
* allow custom bindings

* reorganize

* cleanup
2021-05-20 20:09:14 -05:00
Jason Dove
5fd0cc5469 only initialize search index on startup (#193) 2021-05-19 21:09:01 -05:00
Jason Dove
34ebe9b006 handle "other" jellyfin libraries (#192) 2021-05-19 20:15:16 -05:00
Jason Dove
d7c080cafd optimize plex tv scanner (#190) 2021-05-19 07:22:42 -05:00
Jason Dove
23bab01f2d add multi-part episode tests (#189) 2021-05-18 11:33:34 -05:00
Jason Dove
c7fdacf30f another multi-episode bugfix 2021-05-18 11:00:21 -05:00
Jason Dove
6e6d53d847 multi-episode grouping bugfix 2021-05-18 09:56:04 -05:00
Jason Dove
47e9a319ce add option to keep multi-part episodes together when shuffling (#188)
* add setting to keep multi-part episodes together

* keep multi-part episodes together when shuffling
2021-05-18 08:23:08 -05:00
Jason Dove
9112cb3c1f only scale to even dimensions (#187) 2021-05-16 15:47:44 -05:00
Jason Dove
3ec838da68 handle unauthorized jellyfin server (#186) 2021-05-15 15:47:43 -05:00
Jason Dove
fc5bedc70b update docs for jellyfin [no docker] 2021-05-15 15:39:33 -05:00
Jason Dove
4d86250630 add jellyfin media source (#185)
* wip

* start to add jellyfin tables to db

* code cleanup

* finish adding jellyfin media source

* sync jellyfin libraries

* display list of jellyfin libraries

* toggle jellyfin library sync

* edit jellyfin path replacements

* noop jellyfin scanners

* get jellyfin admin user id on startup

* implement jellyfin disconnect

* add jellyfin libraries to list; start to query jellyfin library items

* code cleanup

* start to project jellyfin movies

* save new jellyfin movies to db

* basic jellyfin movie update

* load jellyfin actor artwork

* load jellyfin movie poster and fan art

* more jellyfin artwork fixes, sync audio streams

* jellyfin playback sort of works

* skip jellyfin movies that are inaccessible

* use ffprobe for jellyfin movie statistics

* code cleanup

* store jellyfin operating system

* more jellyfin movie updates

* update jellyfin movie poster and fan art

* add jellyfin tv types

* sync jellyfin shows

* sync jellyfin seasons

* sync jellyfin episodes

* remove missing jellyfin television items

* delete empty jellyfin seasons and shows

* fix jellyfin updates

* fix indexing jellyfin movie and show languages
2021-05-15 13:14:17 -05:00
Jason Dove
27e0a70d93 add configurable library refresh interval (#184)
* add configurable library refresh interval

* code cleanup
2021-05-14 06:43:44 -05:00
Jason Dove
198e595bc6 add button to copy/clone ffmpeg profile (#183) 2021-05-01 07:45:33 -05:00
Jason Dove
b178b7402b upgrade to ffmpeg 4.4 (#182)
* bump docker images from ffmpeg 4.3 to 4.4 (#181)

* fix ffmpeg 4.4 compatibility
2021-04-28 18:28:07 -05:00
Jason Dove
1c51aed162 Revert "bump docker images from ffmpeg 4.3 to 4.4 (#181)"
This reverts commit ff6a4c5ea2.
2021-04-28 16:15:47 -05:00
Jason Dove
ff6a4c5ea2 bump docker images from ffmpeg 4.3 to 4.4 (#181) 2021-04-28 11:05:41 -05:00
Jason Dove
e515df93fd fix local movie scanner optimization (#180) 2021-04-27 20:13:19 -05:00
Jason Dove
fedc18f7db only show "movie" and "show" libraries from Plex (#179) 2021-04-25 04:24:38 -05:00
Jason Dove
59d75fe08f revert library_name index change, add library_id index (#178) 2021-04-23 08:37:48 -05:00
Jason Dove
49d9b1c714 add library search buttons (#177) 2021-04-22 21:31:19 -05:00
Jason Dove
2f066d5b62 update search docs [no docker] 2021-04-17 21:03:58 -05:00
Jason Dove
63db2edb99 fix plex actor artwork for first few actors (#176) 2021-04-17 15:39:24 -05:00
Jason Dove
5d01276ef3 fix plex actor artwork with newly added media items (#175) 2021-04-17 15:19:23 -05:00
Jason Dove
050aaaa288 fix updating music videos (#174) 2021-04-17 10:24:16 -05:00
Jason Dove
7c07c5f522 fix odd resolution padding; fix updating plex episode artwork (#173)
* fix padding odd resolutions

* fix updating plex episode artwork only as needed
2021-04-17 10:08:20 -05:00
Jason Dove
d8d21996b4 add actors to movies and shows (#172)
* add actor metadata

* show actors in ui

* get full movie/show metadata from plex

* store actor thumbnail url

* rework movie detail page

* metadata fixes

* rework show detail page

* rework artist page

* code cleanup
2021-04-17 09:36:57 -05:00
Jason Dove
e368d4a075 fix collections paging (#171) 2021-04-16 15:59:31 -05:00
Jason Dove
466059e2aa fix add to collection typing lag (#170) 2021-04-15 20:35:07 -05:00
Jason Dove
e951ecb650 fix lag when typing in search bar (#169) 2021-04-15 19:54:19 -05:00
Jason Dove
1d1f53da01 enter to submit all dialogs (#168) 2021-04-14 14:48:08 -05:00
Jason Dove
a854294cb6 allow enter key to submit add to collection dialog (#167) 2021-04-14 14:37:37 -05:00
Jason Dove
f89f3d2225 fix music videos in epg (#166) 2021-04-13 06:20:37 -05:00
Jason Dove
a2700e087c show release notes on home page (#165) 2021-04-11 12:34:41 -05:00
Jason Dove
34fbfce0a5 limit to one playout per channel (#164) 2021-04-11 05:55:26 -05:00
Jason Dove
993293c104 add search docs [no docker] 2021-04-11 05:34:20 -05:00
Jason Dove
ececa62446 fix synchronizing plex show metadata (#163) 2021-04-10 20:57:09 -05:00
Jason Dove
237729e79d add movie, show, artist language buttons. search by english language name (#162) 2021-04-10 20:40:54 -05:00
Jason Dove
9c0ada2df5 fix television metadata (#161) 2021-04-10 18:40:18 -05:00
Jason Dove
dee264597b fix search index warning 2021-04-10 13:07:58 -05:00
Jason Dove
a8db294043 add local libraries doc [no docker] 2021-04-09 18:33:28 -05:00
Jason Dove
a2a63e0120 add creative commons attribution [no docker] 2021-04-09 15:11:22 -05:00
Jason Dove
c7881aec14 remove screenshots [no docker] 2021-04-09 14:39:10 -05:00
Jason Dove
558bdcb6b0 client setup doc updates (#160)
* update jellyfin client setup

* update channels-dvr setup
2021-04-09 13:11:49 -05:00
Jason Dove
24f2b4b727 force music video library scan (#159) 2021-04-09 07:42:13 -05:00
Jason Dove
667887f387 fix television show and season playouts (#158) 2021-04-09 07:24:12 -05:00
Jason Dove
98eb72fcfe skip docker with [no docker] 2021-04-09 06:42:16 -05:00
Jason Dove
c2f92fd054 add github release links to docs [no ci] 2021-04-09 06:39:22 -05:00
Jason Dove
f04ddd3a40 save collection page size (#157) 2021-04-09 06:30:46 -05:00
Jason Dove
aa0942384d fix removing deleted music videos (#156) 2021-04-09 05:34:28 -05:00
Jason Dove
cd100be3a2 relax music video naming requirements (#155) 2021-04-09 05:22:02 -05:00
Jason Dove
2b26a5411c add artists as owners of music videos (#154)
* clean up genre, tag, studio orphans

* enforce foreign keys at connection level

* wip

* fix fragment scroll offset

* fix see all link for music videos

* add fake artist metadata

* not null artist id

* add artist scanning

* remove improperly named music videos

* code cleanup

* add artists to search results and collections

* clean up music video metadata / artist

* add artist view

* show music videos on artist page

* add music video artwork placeholder
2021-04-09 05:10:58 -05:00
Jason Dove
baf81f31cd fix plex server and connection sync (#153) 2021-04-08 18:48:12 -05:00
Jason Dove
bfa290790b trim parsed music video titles (#152) 2021-04-07 16:36:20 -05:00
Jason Dove
b975922a77 only index video stream languages for movies and music videos (#151) 2021-04-07 16:12:39 -05:00
Jason Dove
1a39978a77 try to fix windows builds 2021-04-07 05:34:02 -05:00
Jason Dove
436c9119fa add all search results to collection (#150) 2021-04-06 20:43:44 -05:00
Jason Dove
33642a13ce allow manual ci trigger 2021-04-06 09:21:54 -05:00
Jason Dove
09b349d1cb only index audio language [no ci] 2021-04-06 09:11:39 -05:00
Jason Dove
2be729c10e search show by language (#149) 2021-04-06 07:58:45 -05:00
Jason Dove
0aac702853 search movies and music videos by language (#148) 2021-04-06 07:42:21 -05:00
Jason Dove
3f406ac556 log viewer improvements (#147) 2021-04-06 07:25:05 -05:00
Jason Dove
454e2edf7c add documentation link to ui (#146) 2021-04-06 06:39:48 -05:00
Jason Dove
b3f4fa8c23 add documentation (#145)
* start to reorganize documentation

* revert readme tag changes

* revert readme tag changes pt2

* doc updates; doc theme updates

* doc updates

* publish docs from documentation branch

* use favicon for docs

* create channel

* collections, jellyfin client, schedule items and playout docs

* channels dvr

* tivimate

* scale tivimate screenshots

* add channels dvr server setup

* add copyright and social

* Added UnRAID Docker install, formatting fixes (#100)

Co-authored-by: Thaddeus Cooper <redacted@redacted.co.nz>

* minor doc updates

* readme tweak

* add basic plex documentation

Co-authored-by: suckerface <9060047+suckerface@users.noreply.github.com>
Co-authored-by: Thaddeus Cooper <redacted@redacted.co.nz>
2021-04-06 06:20:49 -05:00
Jason Dove
a6496db58d settings rework (#144)
* add hdhr tuner count setting

* code cleanup
2021-04-05 19:48:17 -05:00
Jason Dove
3eed79b5e1 Merge branch 'main' of github.com:jasongdove/ErsatzTV 2021-04-05 16:19:22 -05:00
Jason Dove
79bfba6428 better search index thread fix (#143)
* Revert "fix search index threading (#141)"

This reverts commit 3fb6da0754.

* better search index thread fix
2021-04-05 16:18:17 -05:00
Jason Dove
9f6d4114a6 Merge branch 'main' of github.com:jasongdove/ErsatzTV 2021-04-05 16:06:28 -05:00
Jason Dove
9809c60924 send all audio streams on hls channels with no preferred language (#142)
* Revert "fix search index threading (#141)"

This reverts commit 3fb6da0754.

* send all audio streams on hls channels with no preferred language
2021-04-05 16:06:13 -05:00
Jason Dove
16072fed1c Revert "fix search index threading (#141)"
This reverts commit 3fb6da0754.
2021-04-05 07:44:42 -05:00
Jason Dove
3fb6da0754 fix search index threading (#141)
* fix search index threading

* code cleanup
2021-04-05 05:41:29 -05:00
Jason Dove
24cdf6295f clean up fragment letter anchor code (#140) 2021-04-04 20:56:47 -05:00
Jason Dove
c1b41e2865 use fragment navigation with letter bar (#139) 2021-04-04 20:30:40 -05:00
Jason Dove
d249e95f12 fix poster width (#138) 2021-04-04 20:13:29 -05:00
Jason Dove
efae005447 use full preferred language names in ui (#137) 2021-04-04 18:30:42 -05:00
Jason Dove
cead787c55 force SAR 1:1 if missing (#136) 2021-04-04 18:00:39 -05:00
Jason Dove
77a69af1a8 sort channels and schedules in playout editor (#135) 2021-04-04 16:24:46 -05:00
Jason Dove
8fea24a3a5 add fallback metadata for music videos (#134) 2021-04-04 15:57:25 -05:00
Jason Dove
6b44873474 add library scan progress detail (#133)
* add library scan progress detail

* scan plex libraries on plex thread
2021-04-04 10:44:10 -05:00
Jason Dove
c5ee5903b2 use table for collections ui (#132) 2021-04-03 16:21:06 -05:00
Jason Dove
526eada48b channels, schedules, playouts paging/sorting (#131)
* add paging to playouts

* add sorting, paging to schedules

* fix channels sorting; add channels paging
2021-04-03 16:07:05 -05:00
Jason Dove
7a0d65a433 fix epg with music videos (#130) 2021-04-03 15:38:12 -05:00
Jason Dove
74c95249c3 add loudness normalization (#129)
* fix music video search result artwork

* add normalize loudness setting

* fix audio normalization

* fix music video thumbnails in collection items view

* fix ef core warnings querying playout item

* implement audio loudness normalization filter
2021-04-03 13:36:11 -05:00
Jason Dove
d4a2197dfa async fixes (#128)
* refactor local metadata provider

* resolve async warnings

* more async fixes
2021-04-03 11:01:20 -05:00
Jason Dove
633586ddba add music videos library (#125)
* add music videos library

* add music video tables

* first pass at music video library scan

* support music videos in playouts

* display music videos in search results and collections

* fix music video thumbnails

* remove some obsolete fields
2021-04-02 18:28:45 -05:00
Jason Dove
da3e05b231 normalize video track timescale (#123) 2021-03-31 23:36:20 +00:00
Jason Dove
9e6de7e2eb use proper type for plex timestamps (#124) 2021-03-31 21:30:30 +00:00
Jason Dove
4097288fed normalize framerate (#122)
* normalize framerate

* simplify audio normalization settings
2021-03-31 09:34:52 +00:00
Jason Dove
90f775aab4 ffmpeg tweaks (#121)
* save reports from ffmpeg concat process

* let ffmpeg determine thread count by default

* disable stdin for ffmpeg processes
2021-03-31 01:08:57 +00:00
Jason Dove
fc33c5cd05 add show title to playout details (#120) 2021-03-30 21:23:02 +00:00
Jason Dove
37eee73ab7 clear search query when clicking nav links (#119) 2021-03-30 21:10:56 +00:00
Jason Dove
e7ebb32a1d navigate to schedule items after creating new schedule (#118) 2021-03-30 11:15:31 +00:00
Jason Dove
9ea4459988 cache artwork async (#117) 2021-03-30 11:09:47 +00:00
Jason Dove
745b03af73 add custom title option to schedule items (#116) 2021-03-29 21:46:03 +00:00
Jason Dove
a62c4ecfcf fix playout builds using duration or multiple (#115) 2021-03-29 20:01:46 +00:00
Jason Dove
c48f0a7d51 don't require preferred language on channels (#114) 2021-03-29 14:43:09 +00:00
Jason Dove
f2c105174b fix stream selection for non-normalized playback (#113) 2021-03-29 14:42:20 +00:00
Jason Dove
076a88230e optimize local library scanning (#112) 2021-03-29 10:34:33 +00:00
Jason Dove
f06a04ed0e fix search index updates for local libraries (#111) 2021-03-29 10:28:38 +00:00
Jason Dove
07d690a31f fix local tv library scanning (#110) 2021-03-29 10:20:18 +00:00
Jason Dove
001453714a fix playback on channel with no preferred language 2021-03-28 18:21:26 -05:00
Jason Dove
d303bc0158 add preferred language (#109)
* add explicit warning for zero/invalid duration media items

* set dateadded on plex media versions

* add media stream table

* save local media streams to db

* save plex media streams to db

* add preferred language settings (no validation)

* use preferred language if possible

* code cleanup

* proper language code validation

* force scan of all libraries to pull in media streams
2021-03-28 21:54:48 +00:00
Jason Dove
51b671dec7 load concat playlist from localhost 2021-03-28 06:48:10 -05:00
Jason Dove
a5e1cc7c3d allow trailing slash in plex path replacement (#108)
* add test for unc path replacement

* allow trailing slash in plex path replacement
2021-03-28 11:32:32 +00:00
Jason Dove
9ba6686c44 iptv route consistency [no ci] (#107)
* use localhost in concat playlist

* expose all playlist artwork under /iptv
2021-03-28 11:32:13 +00:00
Jason Dove
104d4a0cbd fix mixed platform directory mapping (#106)
* sync plex platform and platform version

* fix mixed-platform path replacements
2021-03-28 01:40:40 +00:00
Jason Dove
22c4fe2a27 fix indexing shows without nfo metadata (#105) 2021-03-27 23:32:10 +00:00
Jason Dove
7e0bdfdb40 fix epg channel sorting (#101) 2021-03-26 10:36:06 +00:00
Jason Dove
6bdaca0222 remove unused code [no ci] 2021-03-26 05:33:46 -05:00
Jason Dove
67aa3a5a46 Revert "update docker repos and tagging for ci"
This reverts commit 470fba275b.
2021-03-23 07:42:31 -05:00
Jason Dove
a0332e242c Revert "update docker repos and tagging for release [no ci]"
This reverts commit cd74859d28.
2021-03-23 07:42:20 -05:00
Jason Dove
cd74859d28 update docker repos and tagging for release [no ci] 2021-03-23 06:39:40 -05:00
Jason Dove
470fba275b update docker repos and tagging for ci 2021-03-23 06:20:58 -05:00
Jason Dove
e42b000b7f fix plex sign in (#99) 2021-03-23 02:09:05 +00:00
Jason Dove
489f8d92ff properly store plex timestamps on update (#98) 2021-03-22 02:20:31 +00:00
Jason Dove
527d3c6e4b attach existing episodes to correct show and season when adding nfo metadata (#97) 2021-03-22 01:57:32 +00:00
Jason Dove
c33c037188 use folder.ext when poster.ext is not found for movies or shows (#96) 2021-03-21 21:50:31 +00:00
Jason Dove
4c70d61d48 metadata improvements (#95)
* fix episode fallback metadata processing, fix show fallback metadata year parsing

* fix sort title for "a" and "an"

* add and index studio metadata

* minimize circular logging with search index errors

* update plex movie sort titles as needed

* properly escape search links

* force refreshing all movie/show metadata
2021-03-21 18:43:08 +00:00
Jason Dove
00fdc272e9 remove plex items from index after sign out (#94) 2021-03-21 15:23:53 +00:00
Jason Dove
f04c18c810 index release date for searching (#93) 2021-03-21 01:49:10 +00:00
Jason Dove
eca58dbe7f plex fixes (#92)
* fix updating plex path replacements

* fix adding/removing plex libraries

* fix adding/removing plex servers

* fix initial plex library sync after sign in

* code cleanup
2021-03-21 01:35:18 +00:00
Jason Dove
cf9479d2a9 log search indexing errors and continue indexing (#91) 2021-03-20 21:33:37 +00:00
Jason Dove
b6331331b0 use default ffmpeg profile with new channels (#90) 2021-03-20 20:56:15 +00:00
Jason Dove
ed365cfa43 keep search query in search field (#89)
* upgrade dependencies

* keep search query in search field
2021-03-20 20:45:02 +00:00
Jason Dove
b3a1e71570 only search title by default, allow leading wildcards 2021-03-20 15:32:30 -05:00
Jason Dove
454343d14f prevent ui crash during index rebuild [no ci] 2021-03-20 11:23:36 -05:00
Jason Dove
c0a6677861 optimize memory use during search index rebuild (#88) 2021-03-20 16:08:28 +00:00
Jason Dove
2efcbca2da search overhaul (#87)
* add letter bar with no links

* use lucene for search, add paged search results

* add search index version

* index library_name; rebuild index when folder is missing

* maintain index as local movies change

* fix tests

* maintain index as local shows change

* maintain index as plex movies change

* maintain index as plex shows change

* code cleanup

* add duplicate filter to search

* add links to letter bar

* code cleanup
2021-03-20 15:49:50 +00:00
Jason Dove
f96efa9b2f fix normalize video codec setting 2021-03-19 16:00:23 -05:00
Jason Dove
f46041305c add docs to schedule items page (#86) 2021-03-19 02:10:56 +00:00
Jason Dove
493a496b91 delete orphan plex media sources (#85)
* delete orphan plex media sources

* fix plex db warning on startup
2021-03-19 01:14:18 +00:00
Jason Dove
739d074bc6 optimize local scanning (#84)
* optimize local scanning

* fix artwork updates

* fix adding genres and tags

* fix movie fallback metadata
2021-03-19 00:45:38 +00:00
2094 changed files with 660741 additions and 36064 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
"version": "2020.3.2",
"version": "2022.1.0",
"commands": [
"jb"
]

View File

@@ -1,6 +1,6 @@
[*]
charset=utf-8-bom
charset=utf-8
end_of_line=lf
trim_trailing_whitespace=false
insert_final_newline=false
@@ -79,3 +79,7 @@ indent_size=2
indent_style=space
indent_size=4
tab_width=4
[*.yml]
indent_style = space
indent_size = 2

26
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
version: 2
updates:
- package-ecosystem: nuget
directory: "/"
schedule:
interval: daily
assignees:
- jasongdove
- package-ecosystem: docker
directory: "/docker"
schedule:
interval: daily
assignees:
- jasongdove
- package-ecosystem: docker
directory: "/docker/nvidia"
schedule:
interval: daily
assignees:
- jasongdove
- package-ecosystem: docker
directory: "/docker/vaapi"
schedule:
interval: daily
assignees:
- jasongdove

256
.github/workflows/artifacts.yml vendored Normal file
View File

@@ -0,0 +1,256 @@
name: Build Artifacts
on:
workflow_call:
inputs:
release_tag:
description: 'Release tag'
required: true
type: string
release_version:
description: 'Release version number (e.g. v0.3.7-alpha)'
required: true
type: string
info_version:
description: 'Informational version number (e.g. 0.3.7-alpha)'
required: true
type: string
secrets:
apple_developer_certificate_p12_base64:
required: true
apple_developer_certificate_password:
required: true
ac_username:
required: true
ac_password:
required: true
gh_token:
required: true
jobs:
build_and_upload_mac:
name: Mac Build & Upload
runs-on: ${{ matrix.os }}
if: contains(github.event.head_commit.message, '[no build]') == false
strategy:
matrix:
include:
- os: macos-latest
kind: macOS
target: osx-x64
- os: macos-latest
kind: macOS
target: osx-arm64
steps:
- name: Get the sources
uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '14'
- name: Cache NPM dependencies
uses: bahmutov/npm-install@v1.4.5
with:
working-directory: ErsatzTV/client-app
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
- name: Install dependencies
run: dotnet restore -r "${{ matrix.target}}"
- name: Import Code-Signing Certificates
uses: Apple-Actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.apple_developer_certificate_p12_base64 }}
p12-password: ${{ secrets.apple_developer_certificate_password }}
- name: Calculate Release Name
shell: bash
run: |
release_name="ErsatzTV-${{ inputs.release_version }}-${{ matrix.target }}"
echo "RELEASE_NAME=${release_name}" >> $GITHUB_ENV
- name: Build
shell: bash
run: dotnet publish ErsatzTV/ErsatzTV.csproj --framework net6.0 --runtime "${{ matrix.target }}" -c Release -o publish -p:InformationalVersion="${{ inputs.release_version }}-${{ matrix.target }}" -p:EnableCompressionInSingleFile=true -p:DebugType=Embedded -p:PublishSingleFile=true --self-contained true
- name: Bundle
shell: bash
run: |
brew install coreutils
plutil -replace CFBundleShortVersionString -string "${{ inputs.info_version }}" ErsatzTV-macOS/ErsatzTV-macOS/Info.plist
plutil -replace CFBundleVersion -string "${{ inputs.info_version }}" ErsatzTV-macOS/ErsatzTV-macOS/Info.plist
scripts/macOS/bundle.sh
- name: Sign
shell: bash
run: scripts/macOS/sign.sh
- name: Create DMG
shell: bash
run: |
brew install create-dmg
create-dmg \
--volname "ErsatzTV" \
--volicon "artwork/ErsatzTV.icns" \
--window-pos 200 120 \
--window-size 800 400 \
--icon-size 100 \
--icon "ErsatzTV.app" 200 190 \
--hide-extension "ErsatzTV.app" \
--app-drop-link 600 185 \
--skip-jenkins \
"ErsatzTV.dmg" \
"ErsatzTV.app/"
- name: Notarize
shell: bash
run: |
brew tap mitchellh/gon
brew install mitchellh/gon/gon
gon -log-level=debug -log-json ./gon.json
env:
AC_USERNAME: ${{ secrets.ac_username }}
AC_PASSWORD: ${{ secrets.ac_password }}
- name: Cleanup
shell: bash
run: |
mv ErsatzTV.dmg "${{ env.RELEASE_NAME }}.dmg"
rm -r publish
rm -r ErsatzTV.app
- name: Delete old release assets
uses: mknejp/delete-release-assets@v1
with:
token: ${{ secrets.gh_token }}
tag: ${{ inputs.release_tag }}
fail-if-no-assets: false
assets: |
*${{ matrix.target }}.dmg
- name: Publish
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ inputs.release_tag }}
files: |
${{ env.RELEASE_NAME }}.dmg
env:
GITHUB_TOKEN: ${{ secrets.gh_token }}
build_and_upload:
name: Build & Upload
runs-on: ${{ matrix.os }}
if: contains(github.event.head_commit.message, '[no build]') == false
strategy:
matrix:
include:
- os: ubuntu-latest
kind: linux
target: linux-x64
- os: ubuntu-latest
kind: linux
target: linux-arm
- os: ubuntu-latest
kind: linux
target: linux-arm64
- os: windows-latest
kind: windows
target: win-x64
steps:
- name: Get the sources
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup .NET Core
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '14'
- name: Cache NPM dependencies
uses: bahmutov/npm-install@v1.4.5
with:
working-directory: ErsatzTV/client-app
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
- name: Install dependencies
run: dotnet restore -r "${{ matrix.target }}"
- uses: suisei-cn/actions-download-file@v1
if: ${{ matrix.kind == 'windows' }}
id: downloadffmpeg
name: Download ffmpeg
with:
url: "https://github.com/GyanD/codexffmpeg/releases/download/5.0/ffmpeg-5.0-full_build.7z"
target: ffmpeg/
- name: Build
shell: bash
run: |
# Define some variables for things we need
release_name="ErsatzTV-${{ inputs.release_version }}-${{ matrix.target }}"
echo "RELEASE_NAME=${release_name}" >> $GITHUB_ENV
# Build everything
dotnet publish ErsatzTV/ErsatzTV.csproj --framework net6.0 --runtime "${{ matrix.target }}" -c Release -o "$release_name" -p:InformationalVersion="${{ inputs.release_version }}-${{ matrix.target }}" -p:EnableCompressionInSingleFile=true -p:DebugType=Embedded -p:PublishSingleFile=true --self-contained true
# Build Windows launcher
if [ "${{ matrix.kind }}" == "windows" ]; then
dotnet publish ErsatzTV-Windows/ErsatzTV-Windows.csproj --framework net6.0-windows --runtime "${{ matrix.target }}" -c Release -o "$release_name" -p:InformationalVersion="${{ inputs.release_version }}-${{ matrix.target }}" -p:EnableCompressionInSingleFile=true -p:DebugType=Embedded -p:PublishSingleFile=true --self-contained true
fi
# Download ffmpeg
if [ "${{ matrix.kind }}" == "windows" ]; then
7z e "ffmpeg/${{ steps.downloadffmpeg.outputs.filename }}" -o"$release_name" '*.exe' -r
rm -f "$release_name/ffplay.exe"
fi
# Pack files
if [ "${{ matrix.kind }}" == "windows" ]; then
7z a -tzip "${release_name}.zip" "./${release_name}/*"
else
tar czvf "${release_name}.tar.gz" "$release_name"
fi
# Delete output directory
rm -r "$release_name"
env:
AC_USERNAME: ${{ secrets.ac_username }}
AC_PASSWORD: ${{ secrets.ac_password }}
- name: Delete old release assets
uses: mknejp/delete-release-assets@v1
with:
token: ${{ secrets.gh_token }}
tag: ${{ inputs.release_tag }}
fail-if-no-assets: false
assets: |
*${{ matrix.target }}.zip
*${{ matrix.target }}.tar.gz
- name: Publish
uses: softprops/action-gh-release@v1
with:
prerelease: true
tag_name: ${{ inputs.release_tag }}
files: |
${{ env.RELEASE_NAME }}.zip
${{ env.RELEASE_NAME }}.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.gh_token }}

View File

@@ -1,109 +1,58 @@
name: Build
on:
pull_request:
workflow_dispatch:
push:
branches:
- main
jobs:
build_and_test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ windows-latest, ubuntu-latest, macos-latest ]
calculate_version:
name: Calculate version information
runs-on: ubuntu-latest
steps:
- name: Get the sources
uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --no-restore --verbosity normal
build_and_push:
name: Build & Publish to Docker Hub
needs: build_and_test
runs-on: ubuntu-latest
if: github.event_name == 'push' && !contains(github.event.head_commit.message, '[no ci]')
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Extract Git Tag
- name: Extract Docker Tag
shell: bash
run: |
tag=$(git describe --tags --abbrev=0)
tag2="${tag:1}"
short=$(git rev-parse --short HEAD)
final="${tag2/prealpha/$short}"
final="${tag2/beta/$short}"
echo "GIT_TAG=${final}" >> $GITHUB_ENV
- name: Set up Docker Buildx Base
uses: docker/setup-buildx-action@v1
id: builder-base
- name: Set up Docker Buildx NVIDIA
uses: docker/setup-buildx-action@v1
id: builder-nvidia
- name: Set up Docker Buildx VAAPI
uses: docker/setup-buildx-action@v1
id: builder-vaapi
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and push base
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder-base.outputs.name }}
context: .
file: ./docker/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ env.GIT_TAG }}-docker
tags: |
jasongdove/ersatztv:develop
jasongdove/ersatztv:${{ github.sha }}
- name: Build and push nvidia
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder-nvidia.outputs.name }}
context: .
file: ./docker/nvidia/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ env.GIT_TAG }}-docker-nvidia
tags: |
jasongdove/ersatztv:develop-nvidia
jasongdove/ersatztv:${{ github.sha }}-nvidia
- name: Build and push vaapi
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder-vaapi.outputs.name }}
context: .
file: ./docker/vaapi/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ env.GIT_TAG }}-docker-vaapi
tags: |
jasongdove/ersatztv:develop-vaapi
jasongdove/ersatztv:${{ github.sha }}-vaapi
- name: Extract Artifacts Version
shell: bash
run: |
tag=$(git describe --tags --abbrev=0)
short=$(git rev-parse --short HEAD)
final="${tag/beta/$short}"
echo "ARTIFACTS_VERSION=${final}" >> $GITHUB_ENV
echo "INFO_VERSION=${tag:1}" >> $GITHUB_ENV
outputs:
git_tag: ${{ env.GIT_TAG }}
artifacts_version: ${{ env.ARTIFACTS_VERSION }}
info_version: ${{ env.INFO_VERSION }}
build_and_upload:
uses: jasongdove/ersatztv/.github/workflows/artifacts.yml@main
needs: calculate_version
with:
release_tag: develop
release_version: ${{ needs.calculate_version.outputs.artifacts_version }}
info_version: ${{ needs.calculate_version.outputs.info_version }}
secrets:
apple_developer_certificate_p12_base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
apple_developer_certificate_password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
ac_username: ${{ secrets.AC_USERNAME }}
ac_password: ${{ secrets.AC_PASSWORD }}
gh_token: ${{ secrets.GITHUB_TOKEN }}
build_and_push:
uses: jasongdove/ersatztv/.github/workflows/docker.yml@main
needs: calculate_version
with:
base_version: develop
info_version: ${{ needs.calculate_version.outputs.git_tag }}
tag_version: ${{ github.sha }}
secrets:
docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }}
docker_hub_access_token: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

112
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,112 @@
name: Build & Publish to Docker Hub
on:
workflow_call:
inputs:
base_version:
description: 'Base version (latest or develop)'
required: true
type: string
info_version:
description: 'Informational version number (e.g. 0.3.7-alpha)'
required: true
type: string
tag_version:
description: 'Docker tag version (e.g. v0.3.7)'
required: true
type: string
secrets:
docker_hub_username:
required: true
docker_hub_access_token:
required: true
jobs:
build_and_push:
name: Build & Publish
runs-on: ubuntu-latest
if: contains(github.event.head_commit.message, '[no build]') == false
strategy:
matrix:
include:
- name: base
path: ''
suffix: ''
qemu: false
- name: nvidia
path: 'nvidia/'
suffix: '-nvidia'
qemu: false
- name: vaapi
path: 'vaapi/'
suffix: '-vaapi'
qemu: false
- name: arm32v7
path: 'arm32v7/'
suffix: '-arm'
qemu: true
- name: arm64
path: 'arm64/'
suffix: '-arm64'
qemu: true
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
if: ${{ matrix.qemu == true }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
id: docker-buildx
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.docker_hub_username }}
password: ${{ secrets.docker_hub_access_token }}
- name: Build and push
uses: docker/build-push-action@v3
with:
builder: ${{ steps.docker-buildx.outputs.name }}
context: .
file: ./docker/${{ matrix.path }}Dockerfile
push: true
build-args: |
INFO_VERSION=${{ inputs.info_version }}-docker${{ matrix.suffix }}
tags: |
jasongdove/ersatztv:${{ inputs.base_version }}${{ matrix.suffix }}
jasongdove/ersatztv:${{ inputs.tag_version }}${{ matrix.suffix }}
if: ${{ matrix.name != 'arm64' && matrix.name != 'arm32v7' }}
- name: Build and push
uses: docker/build-push-action@v3
with:
builder: ${{ steps.docker-buildx.outputs.name }}
context: .
file: ./docker/${{ matrix.path }}Dockerfile
push: true
platforms: 'linux/arm64'
build-args: |
INFO_VERSION=${{ inputs.info_version }}-docker${{ matrix.suffix }}
tags: |
jasongdove/ersatztv:${{ inputs.base_version }}${{ matrix.suffix }}
jasongdove/ersatztv:${{ inputs.tag_version }}${{ matrix.suffix }}
if: ${{ matrix.name == 'arm64' }}
- name: Build and push
uses: docker/build-push-action@v3
with:
builder: ${{ steps.docker-buildx.outputs.name }}
context: .
file: ./docker/${{ matrix.path }}Dockerfile
push: true
platforms: 'linux/arm/v7'
build-args: |
INFO_VERSION=${{ inputs.info_version }}-docker${{ matrix.suffix }}
tags: |
jasongdove/ersatztv:${{ inputs.base_version }}${{ matrix.suffix }}
jasongdove/ersatztv:${{ inputs.tag_version }}${{ matrix.suffix }}
if: ${{ matrix.name == 'arm32v7' }}

21
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Publish docs via GitHub Pages
on:
push:
branches:
- main
paths:
- docs/**
- mkdocs.yml
jobs:
build:
name: Deploy docs
runs-on: ubuntu-latest
steps:
- name: Checkout master
uses: actions/checkout@v3
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CUSTOM_DOMAIN: ersatztv.org

30
.github/workflows/pr.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Pull Request
on:
pull_request:
jobs:
build_and_test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ windows-latest, ubuntu-latest, macos-latest ]
steps:
- name: Get the sources
uses: actions/checkout@v3
- name: Setup .NET Core
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --no-restore --verbosity normal

View File

@@ -1,141 +1,53 @@
name: Publish
name: Release
on:
release:
types: [ published ]
jobs:
release:
name: Release
strategy:
matrix:
include:
- os: ubuntu-latest
kind: linux
target: linux-x64
- os: windows-latest
kind: windows
target: win-x64
- os: macos-latest
kind: maxOS
target: osx-x64
runs-on: ${{ matrix.os }}
steps:
- name: Get the sources
uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
- name: Install dependencies
run: dotnet restore
- name: Build
shell: bash
run: |
# Define some variables for things we need
tag=$(git describe --tags --abbrev=0)
release_name="ErsatzTV-$tag-${{ matrix.target }}"
#release_name_cli="ErsatzTV.CommandLine-$tag-${{ matrix.target }}"
# Build everything
dotnet publish ErsatzTV/ErsatzTV.csproj --framework net5.0 --runtime "${{ matrix.target }}" -c Release -o "$release_name" /property:InformationalVersion="${tag:1}-${{ matrix.target }}"
#dotnet publish ErsatzTV.CommandLine/ErsatzTV.CommandLine.csproj --framework net5.0 --runtime "${{ matrix.target }}" -c Release -o "$release_name_cli" /property:InformationalVersion="${tag:1}-${{ matrix.target }}"
# Pack files
if [ "${{ matrix.target }}" == "win-x64" ]; then
7z a -tzip "${release_name}.zip" "./${release_name}/*"
#7z a -tzip "${release_name_cli}.zip" "./${release_name_cli}/*"
else
tar czvf "${release_name}.tar.gz" "$release_name"
#tar czvf "${release_name_cli}.tar.gz" "$release_name_cli"
fi
# Delete output directory
rm -r "$release_name"
#rm -r "$release_name_cli"
- name: Publish
uses: softprops/action-gh-release@v1
with:
prerelease: true
files: |
ErsatzTV*.zip
ErsatzTV*.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build_and_push:
name: Build & Publish to Docker Hub
calculate_version:
name: Calculate version information
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Get the sources
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Extract Git Tag
- name: Extract Docker Tag
shell: bash
run: |
tag=$(git describe --tags --abbrev=0)
echo "GIT_TAG=${tag:1}" >> $GITHUB_ENV
echo "DOCKER_TAG=${tag/-prealpha/}" >> $GITHUB_ENV
- name: Set up Docker Buildx Base
uses: docker/setup-buildx-action@v1
id: builder-base
- name: Set up Docker Buildx NVIDIA
uses: docker/setup-buildx-action@v1
id: builder-nvidia
- name: Set up Docker Buildx VAAPI
uses: docker/setup-buildx-action@v1
id: builder-vaapi
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and push base
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder-base.outputs.name }}
context: .
file: ./docker/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ env.GIT_TAG }}-docker
tags: |
jasongdove/ersatztv:latest
jasongdove/ersatztv:${{ env.DOCKER_TAG }}
- name: Build and push nvidia
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder-nvidia.outputs.name }}
context: .
file: ./docker/nvidia/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ env.GIT_TAG }}-docker-nvidia
tags: |
jasongdove/ersatztv:latest-nvidia
jasongdove/ersatztv:${{ env.DOCKER_TAG }}-nvidia
- name: Build and push vaapi
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder-vaapi.outputs.name }}
context: .
file: ./docker/vaapi/Dockerfile
push: true
build-args: |
INFO_VERSION=${{ env.GIT_TAG }}-docker-vaapi
tags: |
jasongdove/ersatztv:latest-vaapi
jasongdove/ersatztv:${{ env.DOCKER_TAG }}-vaapi
echo "DOCKER_TAG=${tag/-beta/}" >> $GITHUB_ENV
- name: Extract Artifacts Version
shell: bash
run: |
tag=$(git describe --tags --abbrev=0)
echo "ARTIFACTS_VERSION=${tag}" >> $GITHUB_ENV
echo "INFO_VERSION=${tag:1}" >> $GITHUB_ENV
outputs:
git_tag: ${{ env.GIT_TAG }}
docker_tag: ${{ env.DOCKER_TAG }}
artifacts_version: ${{ env.ARTIFACTS_VERSION }}
info_version: ${{ env.INFO_VERSION }}
build_and_upload:
uses: jasongdove/ersatztv/.github/workflows/artifacts.yml@main
needs: calculate_version
with:
release_tag: ${{ needs.calculate_version.outputs.artifacts_version }}
release_version: ${{ needs.calculate_version.outputs.artifacts_version }}
info_version: ${{ needs.calculate_version.outputs.info_version }}
secrets:
apple_developer_certificate_p12_base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
apple_developer_certificate_password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
ac_username: ${{ secrets.AC_USERNAME }}
ac_password: ${{ secrets.AC_PASSWORD }}
gh_token: ${{ secrets.GITHUB_TOKEN }}
build_and_push:
uses: jasongdove/ersatztv/.github/workflows/docker.yml@main
needs: calculate_version
with:
base_version: latest
info_version: ${{ needs.calculate_version.outputs.git_tag }}
tag_version: ${{ needs.calculate_version.outputs.docker_tag }}
secrets:
docker_hub_username: ${{ secrets.DOCKER_HUB_USERNAME }}
docker_hub_access_token: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

22
.github/workflows/vue-lint.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Lint VueJS Files on PR Request
on:
pull_request:
jobs:
vue-lint:
runs-on: ubuntu-latest
steps:
# Checkout the current repo
- name: Checkout current repository
uses: actions/checkout@v3
# Setup NodeJS version 14
- name: Setup NodeJS V14.x.x
uses: actions/setup-node@v3
with:
node-version: '14'
# CD into the current client directory and lint and build the client
- name: Lint and Build the client
run: |
cd ./ErsatzTV/client-app/
npm ci --no-optional
npm run lint
npm run build --if-present

4
.gitignore vendored
View File

@@ -40,3 +40,7 @@ msbuild.wrn
core
scripts/generate-api-sdk/swagger.json
docker-compose.override.yml
ErsatzTV/wwwroot/v2/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "ErsatzTV-macOS"]
path = ErsatzTV-macOS
url = git@github.com:jasongdove/ErsatzTV-macOS.git

1385
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<RootNamespace>ErsatzTV_Windows</RootNamespace>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationIcon>Ersatztv.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="Ersatztv.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.4.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ErsatzTV.Core\ErsatzTV.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Program.cs">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Compile>
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,14 @@
namespace ErsatzTV_Windows;
public static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new TrayApplicationContext());
}
}

View File

@@ -0,0 +1,81 @@
using ErsatzTV.Core;
using System.Diagnostics;
using CliWrap;
namespace ErsatzTV_Windows;
public class TrayApplicationContext : ApplicationContext
{
private readonly NotifyIcon _trayIcon;
private readonly CancellationTokenSource _tokenSource;
public TrayApplicationContext()
{
_trayIcon = new NotifyIcon
{
Icon = new Icon("./Ersatztv.ico"),
ContextMenuStrip = new ContextMenuStrip(),
Visible = true
};
_tokenSource = new CancellationTokenSource();
AddMenuItem("Launch Web UI", LaunchWebUI);
AddMenuItem("Show Logs", ShowLogs);
_trayIcon.ContextMenuStrip.Items.Add(new ToolStripSeparator());
AddMenuItem("Exit", Exit);
string folder = AppContext.BaseDirectory;
string exe = Path.Combine(folder, "ErsatzTV.exe");
if (File.Exists(exe))
{
Cli.Wrap(exe)
.WithWorkingDirectory(folder)
.WithValidation(CommandResultValidation.None)
.ExecuteAsync(_tokenSource.Token);
}
}
private void AddMenuItem(string name, EventHandler action)
{
var item = new ToolStripMenuItem(name);
item.Click += action;
_trayIcon.ContextMenuStrip.Items.Add(item);
}
private void LaunchWebUI(object? sender, EventArgs e)
{
var process = new Process();
process.StartInfo.UseShellExecute = true;
process.StartInfo.FileName = "http://localhost:8409";
process.Start();
}
private void ShowLogs(object? sender, EventArgs e)
{
if (!Directory.Exists(FileSystemLayout.LogsFolder))
{
Directory.CreateDirectory(FileSystemLayout.LogsFolder);
}
var process = new Process();
process.StartInfo.UseShellExecute = true;
process.StartInfo.FileName = FileSystemLayout.LogsFolder;
process.Start();
}
protected override void Dispose(bool disposing)
{
_tokenSource?.Cancel();
base.Dispose(disposing);
}
private void Exit(object? sender, EventArgs e)
{
// Hide tray icon, otherwise it will remain shown until user mouses over it
_trayIcon.Visible = false;
Application.Exit();
}
}

1
ErsatzTV-macOS Submodule

Submodule ErsatzTV-macOS added at 2f3ee16f11

View File

@@ -0,0 +1,14 @@
using System.Globalization;
namespace ErsatzTV.Application.Artists;
public record ArtistViewModel(
string Name,
string Disambiguation,
string Biography,
string Thumbnail,
string FanArt,
List<string> Genres,
List<string> Styles,
List<string> Moods,
List<CultureInfo> Languages);

View File

@@ -0,0 +1,40 @@
using System.Globalization;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Artists;
internal static class Mapper
{
internal static ArtistViewModel ProjectToViewModel(Artist artist, List<string> languages)
{
ArtistMetadata metadata = Optional(artist.ArtistMetadata).Flatten().Head();
return new ArtistViewModel(
metadata.Title,
metadata.Disambiguation,
metadata.Biography,
Artwork(metadata, ArtworkKind.Thumbnail),
Artwork(metadata, ArtworkKind.FanArt),
metadata.Genres.Map(g => g.Name).ToList(),
metadata.Styles.Map(s => s.Name).ToList(),
metadata.Moods.Map(m => m.Name).ToList(),
LanguagesForArtist(languages));
}
private static string Artwork(Metadata metadata, ArtworkKind artworkKind) =>
Optional(metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == artworkKind))
.Match(a => a.Path, string.Empty);
private static List<CultureInfo> LanguagesForArtist(List<string> languages)
{
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
return languages
.Distinct()
.Map(
lang => allCultures.Filter(
ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase)))
.Sequence()
.Flatten()
.ToList();
}
}

View File

@@ -0,0 +1,5 @@
using ErsatzTV.Application.MediaItems;
namespace ErsatzTV.Application.Artists;
public record GetAllArtists : IRequest<List<NamedMediaItemViewModel>>;

View File

@@ -0,0 +1,22 @@
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Core.Interfaces.Repositories;
using static ErsatzTV.Application.MediaItems.Mapper;
namespace ErsatzTV.Application.Artists;
public class GetAllArtistsHandler : IRequestHandler<GetAllArtists, List<NamedMediaItemViewModel>>
{
private readonly IArtistRepository _artistRepository;
public GetAllArtistsHandler(IArtistRepository artistRepository) => _artistRepository = artistRepository;
public Task<List<NamedMediaItemViewModel>> Handle(
GetAllArtists request,
CancellationToken cancellationToken) =>
_artistRepository.GetAllArtists()
.Map(
list => list.Filter(
a => !string.IsNullOrWhiteSpace(
a.ArtistMetadata.HeadOrNone().Match(am => am.Title, () => string.Empty))))
.Map(list => list.Map(ProjectToViewModel).ToList());
}

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Artists;
public record GetArtistById(int ArtistId) : IRequest<Option<ArtistViewModel>>;

View File

@@ -0,0 +1,32 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using static ErsatzTV.Application.Artists.Mapper;
namespace ErsatzTV.Application.Artists;
public class GetArtistByIdHandler : IRequestHandler<GetArtistById, Option<ArtistViewModel>>
{
private readonly IArtistRepository _artistRepository;
private readonly ISearchRepository _searchRepository;
public GetArtistByIdHandler(IArtistRepository artistRepository, ISearchRepository searchRepository)
{
_artistRepository = artistRepository;
_searchRepository = searchRepository;
}
public async Task<Option<ArtistViewModel>> Handle(
GetArtistById request,
CancellationToken cancellationToken)
{
Option<Artist> maybeArtist = await _artistRepository.GetArtist(request.ArtistId);
return await maybeArtist.Match<Task<Option<ArtistViewModel>>>(
async artist =>
{
List<string> mediaCodes = await _searchRepository.GetLanguagesForArtist(artist);
List<string> languageCodes = await _searchRepository.GetAllLanguageCodes(mediaCodes);
return ProjectToViewModel(artist, languageCodes);
},
() => Task.FromResult(Option<ArtistViewModel>.None));
}
}

View File

@@ -1,12 +1,20 @@
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Channels
{
public record ChannelViewModel(
int Id,
string Number,
string Name,
int FFmpegProfileId,
string Logo,
StreamingMode StreamingMode);
}
namespace ErsatzTV.Application.Channels;
public record ChannelViewModel(
int Id,
string Number,
string Name,
string Group,
string Categories,
int FFmpegProfileId,
string Logo,
string PreferredAudioLanguageCode,
StreamingMode StreamingMode,
int? WatermarkId,
int? FallbackFillerId,
int PlayoutCount,
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode);

View File

@@ -1,15 +1,20 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.Channels.Commands
{
public record CreateChannel
(
string Name,
string Number,
int FFmpegProfileId,
string Logo,
StreamingMode StreamingMode) : IRequest<Either<BaseError, ChannelViewModel>>;
}
namespace ErsatzTV.Application.Channels;
public record CreateChannel
(
string Name,
string Number,
string Group,
string Categories,
int FFmpegProfileId,
string Logo,
string PreferredAudioLanguageCode,
StreamingMode StreamingMode,
int? WatermarkId,
int? FallbackFillerId,
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode) : IRequest<Either<BaseError, CreateChannelResult>>;

View File

@@ -1,91 +1,175 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
using MediatR;
using static ErsatzTV.Application.Channels.Mapper;
using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Channels.Commands
namespace ErsatzTV.Application.Channels;
public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseError, CreateChannelResult>>
{
public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseError, ChannelViewModel>>
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public CreateChannelHandler(IDbContextFactory<TvContext> dbContextFactory) => _dbContextFactory = dbContextFactory;
public async Task<Either<BaseError, CreateChannelResult>> Handle(
CreateChannel request,
CancellationToken cancellationToken)
{
private readonly IChannelRepository _channelRepository;
private readonly IFFmpegProfileRepository _ffmpegProfileRepository;
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Channel> validation = await Validate(dbContext, request);
return await validation.Apply(c => PersistChannel(dbContext, c));
}
public CreateChannelHandler(
IChannelRepository channelRepository,
IFFmpegProfileRepository ffmpegProfileRepository)
private static async Task<CreateChannelResult> PersistChannel(TvContext dbContext, Channel channel)
{
await dbContext.Channels.AddAsync(channel);
await dbContext.SaveChangesAsync();
return new CreateChannelResult(channel.Id);
}
private static async Task<Validation<BaseError, Channel>> Validate(TvContext dbContext, CreateChannel request) =>
(ValidateName(request), await ValidateNumber(dbContext, request),
await FFmpegProfileMustExist(dbContext, request),
ValidatePreferredAudioLanguage(request),
ValidatePreferredSubtitleLanguage(request),
await WatermarkMustExist(dbContext, request),
await FillerPresetMustExist(dbContext, request))
.Apply(
(
name,
number,
ffmpegProfileId,
preferredAudioLanguageCode,
preferredSubtitleLanguageCode,
watermarkId,
fillerPresetId) =>
{
var artwork = new List<Artwork>();
if (!string.IsNullOrWhiteSpace(request.Logo))
{
artwork.Add(
new Artwork
{
Path = request.Logo,
ArtworkKind = ArtworkKind.Logo,
DateAdded = DateTime.UtcNow,
DateUpdated = DateTime.UtcNow
});
}
var channel = new Channel(Guid.NewGuid())
{
Name = name,
Number = number,
Group = request.Group,
Categories = request.Categories,
FFmpegProfileId = ffmpegProfileId,
StreamingMode = request.StreamingMode,
Artwork = artwork,
PreferredAudioLanguageCode = preferredAudioLanguageCode,
PreferredSubtitleLanguageCode = preferredSubtitleLanguageCode,
SubtitleMode = request.SubtitleMode,
MusicVideoCreditsMode = request.MusicVideoCreditsMode
};
foreach (int id in watermarkId)
{
channel.WatermarkId = id;
}
foreach (int id in fillerPresetId)
{
channel.FallbackFillerId = id;
}
return channel;
});
private static Validation<BaseError, string> ValidateName(CreateChannel createChannel) =>
createChannel.NotEmpty(c => c.Name)
.Bind(_ => createChannel.NotLongerThan(50)(c => c.Name));
private static Validation<BaseError, string> ValidatePreferredAudioLanguage(CreateChannel createChannel) =>
Optional(createChannel.PreferredAudioLanguageCode ?? string.Empty)
.Filter(
lc => string.IsNullOrWhiteSpace(lc) || CultureInfo.GetCultures(CultureTypes.NeutralCultures).Any(
ci => string.Equals(ci.ThreeLetterISOLanguageName, lc, StringComparison.OrdinalIgnoreCase)))
.ToValidation<BaseError>("Preferred audio language code is invalid");
private static Validation<BaseError, string> ValidatePreferredSubtitleLanguage(CreateChannel createChannel) =>
Optional(createChannel.PreferredSubtitleLanguageCode ?? string.Empty)
.Filter(
lc => string.IsNullOrWhiteSpace(lc) || CultureInfo.GetCultures(CultureTypes.NeutralCultures).Any(
ci => string.Equals(ci.ThreeLetterISOLanguageName, lc, StringComparison.OrdinalIgnoreCase)))
.ToValidation<BaseError>("Preferred subtitle language code is invalid");
private static async Task<Validation<BaseError, string>> ValidateNumber(
TvContext dbContext,
CreateChannel createChannel)
{
Option<Channel> maybeExistingChannel = await dbContext.Channels
.SelectOneAsync(c => c.Number, c => c.Number == createChannel.Number);
return maybeExistingChannel.Match<Validation<BaseError, string>>(
_ => BaseError.New("Channel number must be unique"),
() =>
{
if (Regex.IsMatch(createChannel.Number, Channel.NumberValidator))
{
return createChannel.Number;
}
return BaseError.New("Invalid channel number; two decimals are allowed for subchannels");
});
}
private static Task<Validation<BaseError, int>> FFmpegProfileMustExist(
TvContext dbContext,
CreateChannel createChannel) =>
dbContext.FFmpegProfiles
.CountAsync(p => p.Id == createChannel.FFmpegProfileId)
.Map(Optional)
.Filter(c => c > 0)
.MapT(_ => createChannel.FFmpegProfileId)
.Map(o => o.ToValidation<BaseError>($"FFmpegProfile {createChannel.FFmpegProfileId} does not exist."));
private static async Task<Validation<BaseError, Option<int>>> WatermarkMustExist(
TvContext dbContext,
CreateChannel createChannel)
{
if (createChannel.WatermarkId is null)
{
_channelRepository = channelRepository;
_ffmpegProfileRepository = ffmpegProfileRepository;
return Option<int>.None;
}
public Task<Either<BaseError, ChannelViewModel>> Handle(
CreateChannel request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(PersistChannel)
.Bind(v => v.ToEitherAsync());
return await dbContext.ChannelWatermarks
.CountAsync(w => w.Id == createChannel.WatermarkId)
.Map(Optional)
.Filter(c => c > 0)
.MapT(_ => Optional(createChannel.WatermarkId))
.Map(o => o.ToValidation<BaseError>($"Watermark {createChannel.WatermarkId} does not exist."));
}
private Task<ChannelViewModel> PersistChannel(Channel c) =>
_channelRepository.Add(c).Map(ProjectToViewModel);
private async Task<Validation<BaseError, Channel>> Validate(CreateChannel request) =>
(ValidateName(request), await ValidateNumber(request), await FFmpegProfileMustExist(request))
.Apply(
(name, number, ffmpegProfileId) =>
{
var artwork = new List<Artwork>();
if (!string.IsNullOrWhiteSpace(request.Logo))
{
artwork.Add(
new Artwork
{
Path = request.Logo,
ArtworkKind = ArtworkKind.Logo,
DateAdded = DateTime.UtcNow,
DateUpdated = DateTime.UtcNow
});
}
return new Channel(Guid.NewGuid())
{
Name = name,
Number = number,
FFmpegProfileId = ffmpegProfileId,
StreamingMode = request.StreamingMode,
Artwork = artwork
};
});
private Validation<BaseError, string> ValidateName(CreateChannel createChannel) =>
createChannel.NotEmpty(c => c.Name)
.Bind(_ => createChannel.NotLongerThan(50)(c => c.Name));
private async Task<Validation<BaseError, string>> ValidateNumber(CreateChannel createChannel)
private static async Task<Validation<BaseError, Option<int>>> FillerPresetMustExist(
TvContext dbContext,
CreateChannel createChannel)
{
if (createChannel.FallbackFillerId is null)
{
Option<Channel> maybeExistingChannel = await _channelRepository.GetByNumber(createChannel.Number);
return maybeExistingChannel.Match<Validation<BaseError, string>>(
_ => BaseError.New("Channel number must be unique"),
() =>
{
if (Regex.IsMatch(createChannel.Number, Channel.NumberValidator))
{
return createChannel.Number;
}
return BaseError.New("Invalid channel number; one decimal is allowed for subchannels");
});
return Option<int>.None;
}
private async Task<Validation<BaseError, int>> FFmpegProfileMustExist(CreateChannel createChannel) =>
(await _ffmpegProfileRepository.Get(createChannel.FFmpegProfileId))
.ToValidation<BaseError>($"FFmpegProfile {createChannel.FFmpegProfileId} does not exist.")
.Map(c => c.Id);
return await dbContext.FillerPresets
.Filter(fp => fp.FillerKind == FillerKind.Fallback)
.CountAsync(w => w.Id == createChannel.FallbackFillerId)
.Map(Optional)
.Filter(c => c > 0)
.MapT(_ => Optional(createChannel.FallbackFillerId))
.Map(
o => o.ToValidation<BaseError>(
$"Fallback filler {createChannel.FallbackFillerId} does not exist."));
}
}

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Channels;
public record CreateChannelResult(int ChannelId) : EntityIdResult(ChannelId);

View File

@@ -1,9 +1,5 @@
using System.Threading.Tasks;
using ErsatzTV.Core;
using LanguageExt;
using MediatR;
using ErsatzTV.Core;
namespace ErsatzTV.Application.Channels.Commands
{
public record DeleteChannel(int ChannelId) : IRequest<Either<BaseError, Task>>;
}
namespace ErsatzTV.Application.Channels;
public record DeleteChannel(int ChannelId) : IRequest<Either<BaseError, Task>>;

View File

@@ -1,28 +1,23 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.Channels.Commands
namespace ErsatzTV.Application.Channels;
public class DeleteChannelHandler : IRequestHandler<DeleteChannel, Either<BaseError, Task>>
{
public class DeleteChannelHandler : IRequestHandler<DeleteChannel, Either<BaseError, Task>>
{
private readonly IChannelRepository _channelRepository;
private readonly IChannelRepository _channelRepository;
public DeleteChannelHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public DeleteChannelHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public async Task<Either<BaseError, Task>> Handle(DeleteChannel request, CancellationToken cancellationToken) =>
(await ChannelMustExist(request))
.Map(DoDeletion)
.ToEither<Task>();
public async Task<Either<BaseError, Task>> Handle(DeleteChannel request, CancellationToken cancellationToken) =>
(await ChannelMustExist(request))
.Map(DoDeletion)
.ToEither<Task>();
private Task DoDeletion(int channelId) => _channelRepository.Delete(channelId);
private Task DoDeletion(int channelId) => _channelRepository.Delete(channelId);
private async Task<Validation<BaseError, int>> ChannelMustExist(DeleteChannel deleteChannel) =>
(await _channelRepository.Get(deleteChannel.ChannelId))
.ToValidation<BaseError>($"Channel {deleteChannel.ChannelId} does not exist.")
.Map(c => c.Id);
}
private async Task<Validation<BaseError, int>> ChannelMustExist(DeleteChannel deleteChannel) =>
(await _channelRepository.Get(deleteChannel.ChannelId))
.ToValidation<BaseError>($"Channel {deleteChannel.ChannelId} does not exist.")
.Map(c => c.Id);
}

View File

@@ -1,16 +1,21 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.Channels.Commands
{
public record UpdateChannel
(
int ChannelId,
string Name,
string Number,
int FFmpegProfileId,
string Logo,
StreamingMode StreamingMode) : IRequest<Either<BaseError, ChannelViewModel>>;
}
namespace ErsatzTV.Application.Channels;
public record UpdateChannel
(
int ChannelId,
string Name,
string Number,
string Group,
string Categories,
int FFmpegProfileId,
string Logo,
string PreferredAudioLanguageCode,
StreamingMode StreamingMode,
int? WatermarkId,
int? FallbackFillerId,
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode) : IRequest<Either<BaseError, ChannelViewModel>>;

View File

@@ -1,96 +1,139 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Channels;
using ErsatzTV.Application.Subtitles;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
using MediatR;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.Channels.Mapper;
using static LanguageExt.Prelude;
using Channel = ErsatzTV.Core.Domain.Channel;
namespace ErsatzTV.Application.Channels.Commands
namespace ErsatzTV.Application.Channels;
public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseError, ChannelViewModel>>
{
public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseError, ChannelViewModel>>
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly ChannelWriter<ISubtitleWorkerRequest> _ffmpegWorkerChannel;
public UpdateChannelHandler(
ChannelWriter<ISubtitleWorkerRequest> ffmpegWorkerChannel,
IDbContextFactory<TvContext> dbContextFactory)
{
private readonly IChannelRepository _channelRepository;
public UpdateChannelHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public Task<Either<BaseError, ChannelViewModel>> Handle(
UpdateChannel request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(c => ApplyUpdateRequest(c, request))
.Bind(v => v.ToEitherAsync());
private async Task<ChannelViewModel> ApplyUpdateRequest(Channel c, UpdateChannel update)
{
c.Name = update.Name;
c.Number = update.Number;
c.FFmpegProfileId = update.FFmpegProfileId;
if (!string.IsNullOrWhiteSpace(update.Logo))
{
c.Artwork ??= new List<Artwork>();
Option<Artwork> maybeLogo =
Optional(c.Artwork).Flatten().FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Logo);
maybeLogo.Match(
artwork =>
{
artwork.Path = update.Logo;
artwork.DateUpdated = DateTime.UtcNow;
},
() =>
{
var artwork = new Artwork
{
Path = update.Logo,
DateAdded = DateTime.UtcNow,
DateUpdated = DateTime.UtcNow,
ArtworkKind = ArtworkKind.Logo
};
c.Artwork.Add(artwork);
});
}
c.StreamingMode = update.StreamingMode;
await _channelRepository.Update(c);
return ProjectToViewModel(c);
}
private async Task<Validation<BaseError, Channel>> Validate(UpdateChannel request) =>
(await ChannelMustExist(request), ValidateName(request), await ValidateNumber(request))
.Apply((channelToUpdate, _, _) => channelToUpdate);
private Task<Validation<BaseError, Channel>> ChannelMustExist(UpdateChannel updateChannel) =>
_channelRepository.Get(updateChannel.ChannelId)
.Map(v => v.ToValidation<BaseError>("Channel does not exist."));
private Validation<BaseError, string> ValidateName(UpdateChannel updateChannel) =>
updateChannel.NotEmpty(c => c.Name)
.Bind(_ => updateChannel.NotLongerThan(50)(c => c.Name));
private async Task<Validation<BaseError, string>> ValidateNumber(UpdateChannel updateChannel)
{
Option<Channel> match = await _channelRepository.GetByNumber(updateChannel.Number);
int matchId = match.Map(c => c.Id).IfNone(updateChannel.ChannelId);
if (matchId == updateChannel.ChannelId)
{
if (Regex.IsMatch(updateChannel.Number, Channel.NumberValidator))
{
return updateChannel.Number;
}
return BaseError.New("Invalid channel number; one decimal is allowed for subchannels");
}
return BaseError.New("Channel number must be unique");
}
_ffmpegWorkerChannel = ffmpegWorkerChannel;
_dbContextFactory = dbContextFactory;
}
public async Task<Either<BaseError, ChannelViewModel>> Handle(
UpdateChannel request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Channel> validation = await Validate(dbContext, request);
return await LanguageExtensions.Apply(validation, c => ApplyUpdateRequest(dbContext, c, request));
}
private async Task<ChannelViewModel> ApplyUpdateRequest(TvContext dbContext, Channel c, UpdateChannel update)
{
c.Name = update.Name;
c.Number = update.Number;
c.Group = update.Group;
c.Categories = update.Categories;
c.FFmpegProfileId = update.FFmpegProfileId;
c.PreferredAudioLanguageCode = update.PreferredAudioLanguageCode;
c.PreferredSubtitleLanguageCode = update.PreferredSubtitleLanguageCode;
c.SubtitleMode = update.SubtitleMode;
c.MusicVideoCreditsMode = update.MusicVideoCreditsMode;
c.Artwork ??= new List<Artwork>();
if (!string.IsNullOrWhiteSpace(update.Logo))
{
Option<Artwork> maybeLogo =
Optional(c.Artwork).Flatten().FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Logo);
maybeLogo.Match(
artwork =>
{
artwork.Path = update.Logo;
artwork.DateUpdated = DateTime.UtcNow;
},
() =>
{
var artwork = new Artwork
{
Path = update.Logo,
DateAdded = DateTime.UtcNow,
DateUpdated = DateTime.UtcNow,
ArtworkKind = ArtworkKind.Logo
};
c.Artwork.Add(artwork);
});
}
c.StreamingMode = update.StreamingMode;
c.WatermarkId = update.WatermarkId;
c.FallbackFillerId = update.FallbackFillerId;
await dbContext.SaveChangesAsync();
if (c.SubtitleMode != ChannelSubtitleMode.None)
{
Option<Playout> maybePlayout = await dbContext.Playouts
.SelectOneAsync(p => p.ChannelId, p => p.ChannelId == c.Id);
foreach (Playout playout in maybePlayout)
{
await _ffmpegWorkerChannel.WriteAsync(new ExtractEmbeddedSubtitles(playout.Id));
}
}
return ProjectToViewModel(c);
}
private async Task<Validation<BaseError, Channel>> Validate(TvContext dbContext, UpdateChannel request) =>
(await ChannelMustExist(dbContext, request), ValidateName(request),
await ValidateNumber(dbContext, request),
ValidatePreferredAudioLanguage(request))
.Apply((channelToUpdate, _, _, _) => channelToUpdate);
private static Task<Validation<BaseError, Channel>> ChannelMustExist(
TvContext dbContext,
UpdateChannel updateChannel) =>
dbContext.Channels
.Include(c => c.Artwork)
.Include(c => c.Watermark)
.SelectOneAsync(c => c.Id, c => c.Id == updateChannel.ChannelId)
.Map(o => o.ToValidation<BaseError>("Channel does not exist."));
private static Validation<BaseError, string> ValidateName(UpdateChannel updateChannel) =>
updateChannel.NotEmpty(c => c.Name)
.Bind(_ => updateChannel.NotLongerThan(50)(c => c.Name));
private static async Task<Validation<BaseError, string>> ValidateNumber(
TvContext dbContext,
UpdateChannel updateChannel)
{
int matchId = await dbContext.Channels
.SelectOneAsync(c => c.Number, c => c.Number == updateChannel.Number)
.Match(c => c.Id, () => updateChannel.ChannelId);
if (matchId == updateChannel.ChannelId)
{
if (Regex.IsMatch(updateChannel.Number, Channel.NumberValidator))
{
return updateChannel.Number;
}
return BaseError.New("Invalid channel number; two decimals are allowed for subchannels");
}
return BaseError.New("Channel number must be unique");
}
private static Validation<BaseError, string> ValidatePreferredAudioLanguage(UpdateChannel updateChannel) =>
Optional(updateChannel.PreferredAudioLanguageCode ?? string.Empty)
.Filter(
lc => string.IsNullOrWhiteSpace(lc) || CultureInfo.GetCultures(CultureTypes.NeutralCultures).Any(
ci => string.Equals(ci.ThreeLetterISOLanguageName, lc, StringComparison.OrdinalIgnoreCase)))
.ToValidation<BaseError>("Preferred audio language code is invalid");
}

View File

@@ -1,22 +1,48 @@
using System.Linq;
using ErsatzTV.Core.Api.Channels;
using ErsatzTV.Core.Domain;
using static LanguageExt.Prelude;
namespace ErsatzTV.Application.Channels
namespace ErsatzTV.Application.Channels;
internal static class Mapper
{
internal static class Mapper
{
internal static ChannelViewModel ProjectToViewModel(Channel channel) =>
new(
channel.Id,
channel.Number,
channel.Name,
channel.FFmpegProfileId,
GetLogo(channel),
channel.StreamingMode);
internal static ChannelViewModel ProjectToViewModel(Channel channel) =>
new(
channel.Id,
channel.Number,
channel.Name,
channel.Group,
channel.Categories,
channel.FFmpegProfileId,
GetLogo(channel),
channel.PreferredAudioLanguageCode,
channel.StreamingMode,
channel.WatermarkId,
channel.FallbackFillerId,
channel.Playouts?.Count ?? 0,
channel.PreferredSubtitleLanguageCode,
channel.SubtitleMode,
channel.MusicVideoCreditsMode);
private static string GetLogo(Channel channel) =>
Optional(channel.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Logo))
.Match(a => a.Path, string.Empty);
}
internal static ChannelResponseModel ProjectToResponseModel(Channel channel) =>
new(
channel.Id,
channel.Number,
channel.Name,
channel.FFmpegProfile.Name,
channel.PreferredAudioLanguageCode,
GetStreamingMode(channel));
private static string GetLogo(Channel channel) =>
Optional(channel.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Logo))
.Match(a => a.Path, string.Empty);
private static string GetStreamingMode(Channel channel) =>
channel.StreamingMode switch
{
StreamingMode.TransportStream => "MPEG-TS (Legacy)",
StreamingMode.TransportStreamHybrid => "MPEG-TS",
StreamingMode.HttpLiveStreamingDirect => "HLS Direct",
StreamingMode.HttpLiveStreamingSegmenter => "HLS Segmenter",
_ => throw new ArgumentOutOfRangeException()
};
}

View File

@@ -1,7 +1,3 @@
using System.Collections.Generic;
using MediatR;
namespace ErsatzTV.Application.Channels;
namespace ErsatzTV.Application.Channels.Queries
{
public record GetAllChannels : IRequest<List<ChannelViewModel>>;
}
public record GetAllChannels : IRequest<List<ChannelViewModel>>;

View File

@@ -0,0 +1,5 @@
using ErsatzTV.Core.Api.Channels;
namespace ErsatzTV.Application.Channels;
public record GetAllChannelsForApi : IRequest<List<ChannelResponseModel>>;

View File

@@ -0,0 +1,21 @@
using ErsatzTV.Core.Api.Channels;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using static ErsatzTV.Application.Channels.Mapper;
namespace ErsatzTV.Application.Channels;
public class GetAllChannelsForApiHandler : IRequestHandler<GetAllChannelsForApi, List<ChannelResponseModel>>
{
private readonly IChannelRepository _channelRepository;
public GetAllChannelsForApiHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public async Task<List<ChannelResponseModel>> Handle(
GetAllChannelsForApi request,
CancellationToken cancellationToken)
{
IEnumerable<Channel> channels = Optional(await _channelRepository.GetAll()).Flatten();
return channels.Map(ProjectToResponseModel).ToList();
}
}

View File

@@ -1,21 +1,14 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core.Interfaces.Repositories;
using MediatR;
using ErsatzTV.Core.Interfaces.Repositories;
using static ErsatzTV.Application.Channels.Mapper;
using static LanguageExt.Prelude;
namespace ErsatzTV.Application.Channels.Queries
namespace ErsatzTV.Application.Channels;
public class GetAllChannelsHandler : IRequestHandler<GetAllChannels, List<ChannelViewModel>>
{
public class GetAllChannelsHandler : IRequestHandler<GetAllChannels, List<ChannelViewModel>>
{
private readonly IChannelRepository _channelRepository;
private readonly IChannelRepository _channelRepository;
public GetAllChannelsHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public GetAllChannelsHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public async Task<List<ChannelViewModel>> Handle(GetAllChannels request, CancellationToken cancellationToken) =>
Optional(await _channelRepository.GetAll()).Flatten().Map(ProjectToViewModel).ToList();
}
public async Task<List<ChannelViewModel>> Handle(GetAllChannels request, CancellationToken cancellationToken) =>
Optional(await _channelRepository.GetAll()).Flatten().Map(ProjectToViewModel).ToList();
}

View File

@@ -1,7 +1,3 @@
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.Channels;
namespace ErsatzTV.Application.Channels.Queries
{
public record GetChannelById(int Id) : IRequest<Option<ChannelViewModel>>;
}
public record GetChannelById(int Id) : IRequest<Option<ChannelViewModel>>;

View File

@@ -1,20 +1,15 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
using MediatR;
using ErsatzTV.Core.Interfaces.Repositories;
using static ErsatzTV.Application.Channels.Mapper;
namespace ErsatzTV.Application.Channels.Queries
namespace ErsatzTV.Application.Channels;
public class GetChannelByIdHandler : IRequestHandler<GetChannelById, Option<ChannelViewModel>>
{
public class GetChannelByIdHandler : IRequestHandler<GetChannelById, Option<ChannelViewModel>>
{
private readonly IChannelRepository _channelRepository;
private readonly IChannelRepository _channelRepository;
public GetChannelByIdHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public GetChannelByIdHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public Task<Option<ChannelViewModel>> Handle(GetChannelById request, CancellationToken cancellationToken) =>
_channelRepository.Get(request.Id)
.MapT(ProjectToViewModel);
}
public Task<Option<ChannelViewModel>> Handle(GetChannelById request, CancellationToken cancellationToken) =>
_channelRepository.Get(request.Id)
.MapT(ProjectToViewModel);
}

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Channels;
public record GetChannelByNumber(string ChannelNumber) : IRequest<Option<ChannelViewModel>>;

View File

@@ -0,0 +1,14 @@
using ErsatzTV.Core.Interfaces.Repositories;
using static ErsatzTV.Application.Channels.Mapper;
namespace ErsatzTV.Application.Channels;
public class GetChannelByNumberHandler : IRequestHandler<GetChannelByNumber, Option<ChannelViewModel>>
{
private readonly IChannelRepository _channelRepository;
public GetChannelByNumberHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public Task<Option<ChannelViewModel>> Handle(GetChannelByNumber request, CancellationToken cancellationToken) =>
_channelRepository.GetByNumber(request.ChannelNumber).MapT(ProjectToViewModel);
}

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Channels;
public record GetChannelFramerate(string ChannelNumber) : IRequest<Option<int>>;

View File

@@ -0,0 +1,113 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Application.Channels;
public class GetChannelFramerateHandler : IRequestHandler<GetChannelFramerate, Option<int>>
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly ILogger<GetChannelFramerateHandler> _logger;
public GetChannelFramerateHandler(
IDbContextFactory<TvContext> dbContextFactory,
ILogger<GetChannelFramerateHandler> logger)
{
_dbContextFactory = dbContextFactory;
_logger = logger;
}
public async Task<Option<int>> Handle(GetChannelFramerate request, CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
FFmpegProfile ffmpegProfile = await dbContext.Channels
.Filter(c => c.Number == request.ChannelNumber)
.Include(c => c.FFmpegProfile)
.Map(c => c.FFmpegProfile)
.SingleAsync(cancellationToken);
if (!ffmpegProfile.NormalizeFramerate)
{
return Option<int>.None;
}
// TODO: expand to check everything in collection rather than what's scheduled?
_logger.LogDebug("Checking frame rates for channel {ChannelNumber}", request.ChannelNumber);
List<Playout> playouts = await dbContext.Playouts
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as Movie).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as Episode).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as Song).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as MusicVideo).MediaVersions)
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)
.ThenInclude(mi => (mi as OtherVideo).MediaVersions)
.Filter(p => p.Channel.Number == request.ChannelNumber)
.ToListAsync(cancellationToken);
var frameRates = playouts.Map(p => p.Items.Map(i => i.MediaItem.GetHeadVersion()))
.Flatten()
.Map(mv => mv.RFrameRate)
.ToList();
var distinct = frameRates.Distinct().ToList();
if (distinct.Count > 1)
{
// TODO: something more intelligent than minimum framerate?
int result = frameRates.Map(ParseFrameRate).Min();
if (result < 24)
{
_logger.LogInformation(
"Normalizing frame rate for channel {ChannelNumber} from {Distinct} to {FrameRate} instead of min value {MinFrameRate}",
request.ChannelNumber,
distinct,
24,
result);
return 24;
}
_logger.LogInformation(
"Normalizing frame rate for channel {ChannelNumber} from {Distinct} to {FrameRate}",
request.ChannelNumber,
distinct,
result);
return result;
}
_logger.LogInformation(
"All content on channel {ChannelNumber} has the same frame rate of {FrameRate}; will not normalize",
request.ChannelNumber,
distinct[0]);
return None;
}
private int ParseFrameRate(string frameRate)
{
if (!int.TryParse(frameRate, out int fr))
{
string[] split = (frameRate ?? string.Empty).Split("/");
if (int.TryParse(split[0], out int left) && int.TryParse(split[1], out int right))
{
fr = (int)Math.Round(left / (double)right);
}
else
{
fr = 24;
}
}
return fr;
}
}

View File

@@ -1,7 +1,5 @@
using ErsatzTV.Core.Iptv;
using MediatR;
namespace ErsatzTV.Application.Channels.Queries
{
public record GetChannelGuide(string Scheme, string Host) : IRequest<ChannelGuide>;
}
namespace ErsatzTV.Application.Channels;
public record GetChannelGuide(string Scheme, string Host) : IRequest<ChannelGuide>;

View File

@@ -1,20 +1,23 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Iptv;
using LanguageExt;
using MediatR;
using Microsoft.IO;
namespace ErsatzTV.Application.Channels.Queries
namespace ErsatzTV.Application.Channels;
public class GetChannelGuideHandler : IRequestHandler<GetChannelGuide, ChannelGuide>
{
public class GetChannelGuideHandler : IRequestHandler<GetChannelGuide, ChannelGuide>
private readonly IChannelRepository _channelRepository;
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
public GetChannelGuideHandler(
IChannelRepository channelRepository,
RecyclableMemoryStreamManager recyclableMemoryStreamManager)
{
private readonly IChannelRepository _channelRepository;
public GetChannelGuideHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public Task<ChannelGuide> Handle(GetChannelGuide request, CancellationToken cancellationToken) =>
_channelRepository.GetAllForGuide()
.Map(channels => new ChannelGuide(request.Scheme, request.Host, channels));
_channelRepository = channelRepository;
_recyclableMemoryStreamManager = recyclableMemoryStreamManager;
}
public Task<ChannelGuide> Handle(GetChannelGuide request, CancellationToken cancellationToken) =>
_channelRepository.GetAllForGuide()
.Map(channels => new ChannelGuide(_recyclableMemoryStreamManager, request.Scheme, request.Host, channels));
}

View File

@@ -1,8 +1,5 @@
using System.Collections.Generic;
using ErsatzTV.Core.Hdhr;
using MediatR;
using ErsatzTV.Core.Hdhr;
namespace ErsatzTV.Application.Channels.Queries
{
public record GetChannelLineup(string Scheme, string Host) : IRequest<List<LineupItem>>;
}
namespace ErsatzTV.Application.Channels;
public record GetChannelLineup(string Scheme, string Host) : IRequest<List<LineupItem>>;

View File

@@ -1,22 +1,15 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core.Hdhr;
using ErsatzTV.Core.Hdhr;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.Channels.Queries
namespace ErsatzTV.Application.Channels;
public class GetChannelLineupHandler : IRequestHandler<GetChannelLineup, List<LineupItem>>
{
public class GetChannelLineupHandler : IRequestHandler<GetChannelLineup, List<LineupItem>>
{
private readonly IChannelRepository _channelRepository;
private readonly IChannelRepository _channelRepository;
public GetChannelLineupHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public GetChannelLineupHandler(IChannelRepository channelRepository) => _channelRepository = channelRepository;
public Task<List<LineupItem>> Handle(GetChannelLineup request, CancellationToken cancellationToken) =>
_channelRepository.GetAll()
.Map(channels => channels.Map(c => new LineupItem(request.Scheme, request.Host, c)).ToList());
}
public Task<List<LineupItem>> Handle(GetChannelLineup request, CancellationToken cancellationToken) =>
_channelRepository.GetAll()
.Map(channels => channels.Map(c => new LineupItem(request.Scheme, request.Host, c)).ToList());
}

View File

@@ -1,7 +1,5 @@
using ErsatzTV.Core.Iptv;
using MediatR;
namespace ErsatzTV.Application.Channels.Queries
{
public record GetChannelPlaylist(string Scheme, string Host) : IRequest<ChannelPlaylist>;
}
namespace ErsatzTV.Application.Channels;
public record GetChannelPlaylist(string Scheme, string Host, string Mode) : IRequest<ChannelPlaylist>;

View File

@@ -1,21 +1,50 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Iptv;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.Channels.Queries
namespace ErsatzTV.Application.Channels;
public class GetChannelPlaylistHandler : IRequestHandler<GetChannelPlaylist, ChannelPlaylist>
{
public class GetChannelPlaylistHandler : IRequestHandler<GetChannelPlaylist, ChannelPlaylist>
private readonly IChannelRepository _channelRepository;
public GetChannelPlaylistHandler(IChannelRepository channelRepository) =>
_channelRepository = channelRepository;
public Task<ChannelPlaylist> Handle(GetChannelPlaylist request, CancellationToken cancellationToken) =>
_channelRepository.GetAll()
.Map(channels => EnsureMode(channels, request.Mode))
.Map(channels => new ChannelPlaylist(request.Scheme, request.Host, channels));
private static List<Channel> EnsureMode(IEnumerable<Channel> channels, string mode)
{
private readonly IChannelRepository _channelRepository;
var result = new List<Channel>();
foreach (Channel channel in channels)
{
switch (mode.ToLowerInvariant())
{
case "segmenter":
channel.StreamingMode = StreamingMode.HttpLiveStreamingSegmenter;
result.Add(channel);
break;
case "hls-direct":
channel.StreamingMode = StreamingMode.HttpLiveStreamingDirect;
result.Add(channel);
break;
case "ts-legacy":
channel.StreamingMode = StreamingMode.TransportStream;
result.Add(channel);
break;
case "ts":
channel.StreamingMode = StreamingMode.TransportStreamHybrid;
result.Add(channel);
break;
default:
result.Add(channel);
break;
}
}
public GetChannelPlaylistHandler(IChannelRepository channelRepository) =>
_channelRepository = channelRepository;
public Task<ChannelPlaylist> Handle(GetChannelPlaylist request, CancellationToken cancellationToken) =>
_channelRepository.GetAll()
.Map(channels => new ChannelPlaylist(request.Scheme, request.Host, channels));
return result;
}
}

View File

@@ -0,0 +1,5 @@
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Configuration;
public record SaveConfigElementByKey(ConfigElementKey Key, string Value) : IRequest<Unit>;

View File

@@ -0,0 +1,17 @@
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Configuration;
public class SaveConfigElementByKeyHandler : IRequestHandler<SaveConfigElementByKey, Unit>
{
private readonly IConfigElementRepository _configElementRepository;
public SaveConfigElementByKeyHandler(IConfigElementRepository configElementRepository) =>
_configElementRepository = configElementRepository;
public async Task<Unit> Handle(SaveConfigElementByKey request, CancellationToken cancellationToken)
{
await _configElementRepository.Upsert(request.Key, request.Value);
return Unit.Default;
}
}

View File

@@ -0,0 +1,5 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Configuration;
public record UpdateGeneralSettings(GeneralSettingsViewModel GeneralSettings) : IRequest<Either<BaseError, Unit>>;

View File

@@ -0,0 +1,32 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using Serilog.Core;
namespace ErsatzTV.Application.Configuration;
public class UpdateGeneralSettingsHandler : IRequestHandler<UpdateGeneralSettings, Either<BaseError, Unit>>
{
private readonly IConfigElementRepository _configElementRepository;
private readonly LoggingLevelSwitch _loggingLevelSwitch;
public UpdateGeneralSettingsHandler(
LoggingLevelSwitch loggingLevelSwitch,
IConfigElementRepository configElementRepository)
{
_loggingLevelSwitch = loggingLevelSwitch;
_configElementRepository = configElementRepository;
}
public async Task<Either<BaseError, Unit>> Handle(
UpdateGeneralSettings request,
CancellationToken cancellationToken) => await ApplyUpdate(request.GeneralSettings);
private async Task<Unit> ApplyUpdate(GeneralSettingsViewModel generalSettings)
{
await _configElementRepository.Upsert(ConfigElementKey.MinimumLogLevel, generalSettings.MinimumLogLevel);
_loggingLevelSwitch.MinimumLevel = generalSettings.MinimumLogLevel;
return Unit.Default;
}
}

View File

@@ -0,0 +1,5 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Configuration;
public record UpdateLibraryRefreshInterval(int LibraryRefreshInterval) : IRequest<Either<BaseError, Unit>>;

View File

@@ -0,0 +1,31 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Configuration;
public class UpdateLibraryRefreshIntervalHandler :
IRequestHandler<UpdateLibraryRefreshInterval, Either<BaseError, Unit>>
{
private readonly IConfigElementRepository _configElementRepository;
public UpdateLibraryRefreshIntervalHandler(IConfigElementRepository configElementRepository) =>
_configElementRepository = configElementRepository;
public Task<Either<BaseError, Unit>> Handle(
UpdateLibraryRefreshInterval request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(
_ => _configElementRepository.Upsert(
ConfigElementKey.LibraryRefreshInterval,
request.LibraryRefreshInterval))
.Bind(v => v.ToEitherAsync());
private static Task<Validation<BaseError, Unit>> Validate(UpdateLibraryRefreshInterval request) =>
Optional(request.LibraryRefreshInterval)
.Where(lri => lri > 0)
.Map(_ => Unit.Default)
.ToValidation<BaseError>("Tuner count must be greater than zero")
.AsTask();
}

View File

@@ -0,0 +1,5 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Configuration;
public record UpdatePlayoutSettings(PlayoutSettingsViewModel PlayoutSettings) : IRequest<Either<BaseError, Unit>>;

View File

@@ -0,0 +1,62 @@
using System.Threading.Channels;
using ErsatzTV.Application.Playouts;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Scheduling;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Configuration;
public class UpdatePlayoutSettingsHandler : IRequestHandler<UpdatePlayoutSettings, Either<BaseError, Unit>>
{
private readonly IConfigElementRepository _configElementRepository;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
public UpdatePlayoutSettingsHandler(
IConfigElementRepository configElementRepository,
IDbContextFactory<TvContext> dbContextFactory,
ChannelWriter<IBackgroundServiceRequest> workerChannel)
{
_configElementRepository = configElementRepository;
_dbContextFactory = dbContextFactory;
_workerChannel = workerChannel;
}
public async Task<Either<BaseError, Unit>> Handle(
UpdatePlayoutSettings request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Unit> validation = await Validate(request);
return await validation.Apply<Unit, Unit>(_ => ApplyUpdate(dbContext, request.PlayoutSettings));
}
private async Task<Unit> ApplyUpdate(TvContext dbContext, PlayoutSettingsViewModel playoutSettings)
{
await _configElementRepository.Upsert(ConfigElementKey.PlayoutDaysToBuild, playoutSettings.DaysToBuild);
await _configElementRepository.Upsert(
ConfigElementKey.PlayoutSkipMissingItems,
playoutSettings.SkipMissingItems);
// continue all playouts to proper number of days
List<Playout> playouts = await dbContext.Playouts
.Include(p => p.Channel)
.ToListAsync();
foreach (int playoutId in playouts.OrderBy(p => decimal.Parse(p.Channel.Number)).Map(p => p.Id))
{
await _workerChannel.WriteAsync(new BuildPlayout(playoutId, PlayoutBuildMode.Continue));
}
return Unit.Default;
}
private static Task<Validation<BaseError, Unit>> Validate(UpdatePlayoutSettings request) =>
Optional(request.PlayoutSettings.DaysToBuild)
.Where(days => days > 0)
.Map(_ => Unit.Default)
.ToValidation<BaseError>("Days to build must be greater than zero")
.AsTask();
}

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Configuration;
public record ConfigElementViewModel(string Key, string Value);

View File

@@ -0,0 +1,8 @@
using Serilog.Events;
namespace ErsatzTV.Application.Configuration;
public class GeneralSettingsViewModel
{
public LogEventLevel MinimumLogLevel { get; set; }
}

View File

@@ -0,0 +1,9 @@
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Configuration;
internal static class Mapper
{
internal static ConfigElementViewModel ProjectToViewModel(ConfigElement element) =>
new(element.Key, element.Value);
}

View File

@@ -0,0 +1,7 @@
namespace ErsatzTV.Application.Configuration;
public class PlayoutSettingsViewModel
{
public int DaysToBuild { get; set; }
public bool SkipMissingItems { get; set; }
}

View File

@@ -0,0 +1,5 @@
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Configuration;
public record GetConfigElementByKey(ConfigElementKey Key) : IRequest<Option<ConfigElementViewModel>>;

View File

@@ -0,0 +1,17 @@
using ErsatzTV.Core.Interfaces.Repositories;
using static ErsatzTV.Application.Configuration.Mapper;
namespace ErsatzTV.Application.Configuration;
public class GetConfigElementByKeyHandler : IRequestHandler<GetConfigElementByKey, Option<ConfigElementViewModel>>
{
private readonly IConfigElementRepository _configElementRepository;
public GetConfigElementByKeyHandler(IConfigElementRepository configElementRepository) =>
_configElementRepository = configElementRepository;
public Task<Option<ConfigElementViewModel>> Handle(
GetConfigElementByKey request,
CancellationToken cancellationToken) =>
_configElementRepository.Get(request.Key).MapT(ProjectToViewModel);
}

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Configuration;
public record GetGeneralSettings : IRequest<GeneralSettingsViewModel>;

View File

@@ -0,0 +1,24 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using Serilog.Events;
namespace ErsatzTV.Application.Configuration;
public class GetGeneralSettingsHandler : IRequestHandler<GetGeneralSettings, GeneralSettingsViewModel>
{
private readonly IConfigElementRepository _configElementRepository;
public GetGeneralSettingsHandler(IConfigElementRepository configElementRepository) =>
_configElementRepository = configElementRepository;
public async Task<GeneralSettingsViewModel> Handle(GetGeneralSettings request, CancellationToken cancellationToken)
{
Option<LogEventLevel> maybeLogLevel =
await _configElementRepository.GetValue<LogEventLevel>(ConfigElementKey.MinimumLogLevel);
return new GeneralSettingsViewModel
{
MinimumLogLevel = await maybeLogLevel.IfNoneAsync(LogEventLevel.Information)
};
}
}

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Configuration;
public record GetLibraryRefreshInterval : IRequest<int>;

View File

@@ -0,0 +1,16 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Configuration;
public class GetLibraryRefreshIntervalHandler : IRequestHandler<GetLibraryRefreshInterval, int>
{
private readonly IConfigElementRepository _configElementRepository;
public GetLibraryRefreshIntervalHandler(IConfigElementRepository configElementRepository) =>
_configElementRepository = configElementRepository;
public Task<int> Handle(GetLibraryRefreshInterval request, CancellationToken cancellationToken) =>
_configElementRepository.GetValue<int>(ConfigElementKey.LibraryRefreshInterval)
.Map(result => result.IfNone(6));
}

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Configuration;
public record GetPlayoutSettings : IRequest<PlayoutSettingsViewModel>;

View File

@@ -0,0 +1,26 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Configuration;
public class GetPlayoutSettingsHandler : IRequestHandler<GetPlayoutSettings, PlayoutSettingsViewModel>
{
private readonly IConfigElementRepository _configElementRepository;
public GetPlayoutSettingsHandler(IConfigElementRepository configElementRepository) =>
_configElementRepository = configElementRepository;
public async Task<PlayoutSettingsViewModel> Handle(GetPlayoutSettings request, CancellationToken cancellationToken)
{
Option<int> daysToBuild = await _configElementRepository.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild);
Option<bool> skipMissingItems =
await _configElementRepository.GetValue<bool>(ConfigElementKey.PlayoutSkipMissingItems);
return new PlayoutSettingsViewModel
{
DaysToBuild = await daysToBuild.IfNoneAsync(2),
SkipMissingItems = await skipMissingItems.IfNoneAsync(false)
};
}
}

View File

@@ -0,0 +1,5 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Emby;
public record DisconnectEmby : IRequest<Either<BaseError, Unit>>;

View File

@@ -0,0 +1,41 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Locking;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
namespace ErsatzTV.Application.Emby;
public class DisconnectEmbyHandler : IRequestHandler<DisconnectEmby, Either<BaseError, Unit>>
{
private readonly IEmbySecretStore _embySecretStore;
private readonly IEntityLocker _entityLocker;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly ISearchIndex _searchIndex;
public DisconnectEmbyHandler(
IMediaSourceRepository mediaSourceRepository,
IEmbySecretStore embySecretStore,
IEntityLocker entityLocker,
ISearchIndex searchIndex)
{
_mediaSourceRepository = mediaSourceRepository;
_embySecretStore = embySecretStore;
_entityLocker = entityLocker;
_searchIndex = searchIndex;
}
public async Task<Either<BaseError, Unit>> Handle(
DisconnectEmby request,
CancellationToken cancellationToken)
{
List<int> ids = await _mediaSourceRepository.DeleteAllEmby();
await _searchIndex.RemoveItems(ids);
_searchIndex.Commit();
await _embySecretStore.DeleteAll();
_entityLocker.UnlockRemoteMediaSource<EmbyMediaSource>();
return Unit.Default;
}
}

View File

@@ -0,0 +1,6 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Emby;
namespace ErsatzTV.Application.Emby;
public record SaveEmbySecrets(EmbySecrets Secrets) : IRequest<Either<BaseError, Unit>>;

View File

@@ -0,0 +1,56 @@
using System.Threading.Channels;
using ErsatzTV.Core;
using ErsatzTV.Core.Emby;
using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Emby;
public class SaveEmbySecretsHandler : IRequestHandler<SaveEmbySecrets, Either<BaseError, Unit>>
{
private readonly ChannelWriter<IEmbyBackgroundServiceRequest> _channel;
private readonly IEmbyApiClient _embyApiClient;
private readonly IEmbySecretStore _embySecretStore;
private readonly IMediaSourceRepository _mediaSourceRepository;
public SaveEmbySecretsHandler(
IEmbySecretStore embySecretStore,
IEmbyApiClient embyApiClient,
IMediaSourceRepository mediaSourceRepository,
ChannelWriter<IEmbyBackgroundServiceRequest> channel)
{
_embySecretStore = embySecretStore;
_embyApiClient = embyApiClient;
_mediaSourceRepository = mediaSourceRepository;
_channel = channel;
}
public Task<Either<BaseError, Unit>> Handle(SaveEmbySecrets request, CancellationToken cancellationToken) =>
Validate(request)
.MapT(PerformSave)
.Bind(v => v.ToEitherAsync());
private async Task<Validation<BaseError, Parameters>> Validate(SaveEmbySecrets request)
{
Either<BaseError, EmbyServerInformation> maybeServerInformation = await _embyApiClient
.GetServerInformation(request.Secrets.Address, request.Secrets.ApiKey);
return maybeServerInformation.Match(
info => Validation<BaseError, Parameters>.Success(new Parameters(request.Secrets, info)),
error => error);
}
private async Task<Unit> PerformSave(Parameters parameters)
{
await _embySecretStore.SaveSecrets(parameters.Secrets);
await _mediaSourceRepository.UpsertEmby(
parameters.Secrets.Address,
parameters.ServerInformation.ServerName,
parameters.ServerInformation.OperatingSystem);
await _channel.WriteAsync(new SynchronizeEmbyMediaSources());
return Unit.Default;
}
private record Parameters(EmbySecrets Secrets, EmbyServerInformation ServerInformation);
}

View File

@@ -0,0 +1,6 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Emby;
public record SynchronizeEmbyCollections(int EmbyMediaSourceId) : IRequest<Either<BaseError, Unit>>,
IEmbyBackgroundServiceRequest;

View File

@@ -0,0 +1,73 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Emby;
using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Emby;
public class SynchronizeEmbyCollectionsHandler : IRequestHandler<SynchronizeEmbyCollections, Either<BaseError, Unit>>
{
private readonly IEmbySecretStore _embySecretStore;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IEmbyCollectionScanner _scanner;
public SynchronizeEmbyCollectionsHandler(
IMediaSourceRepository mediaSourceRepository,
IEmbySecretStore embySecretStore,
IEmbyCollectionScanner scanner)
{
_mediaSourceRepository = mediaSourceRepository;
_embySecretStore = embySecretStore;
_scanner = scanner;
}
public async Task<Either<BaseError, Unit>> Handle(
SynchronizeEmbyCollections request,
CancellationToken cancellationToken)
{
Validation<BaseError, ConnectionParameters> validation = await Validate(request);
return await validation.Match(
SynchronizeCollections,
error => Task.FromResult<Either<BaseError, Unit>>(error.Join()));
}
private Task<Validation<BaseError, ConnectionParameters>> Validate(SynchronizeEmbyCollections request) =>
MediaSourceMustExist(request)
.BindT(MediaSourceMustHaveActiveConnection)
.BindT(MediaSourceMustHaveApiKey);
private Task<Validation<BaseError, EmbyMediaSource>> MediaSourceMustExist(
SynchronizeEmbyCollections request) =>
_mediaSourceRepository.GetEmby(request.EmbyMediaSourceId)
.Map(o => o.ToValidation<BaseError>("Emby media source does not exist."));
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
EmbyMediaSource embyMediaSource)
{
Option<EmbyConnection> maybeConnection = embyMediaSource.Connections.HeadOrNone();
return maybeConnection.Map(connection => new ConnectionParameters(connection))
.ToValidation<BaseError>("Emby media source requires an active connection");
}
private async Task<Validation<BaseError, ConnectionParameters>> MediaSourceMustHaveApiKey(
ConnectionParameters connectionParameters)
{
EmbySecrets secrets = await _embySecretStore.ReadSecrets();
return Optional(secrets.Address == connectionParameters.ActiveConnection.Address)
.Where(match => match)
.Map(_ => connectionParameters with { ApiKey = secrets.ApiKey })
.ToValidation<BaseError>("Emby media source requires an api key");
}
private async Task<Either<BaseError, Unit>> SynchronizeCollections(ConnectionParameters connectionParameters) =>
await _scanner.ScanCollections(
connectionParameters.ActiveConnection.Address,
connectionParameters.ApiKey);
private record ConnectionParameters(EmbyConnection ActiveConnection)
{
public string ApiKey { get; set; }
}
}

View File

@@ -0,0 +1,6 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Emby;
public record SynchronizeEmbyLibraries(int EmbyMediaSourceId) : IRequest<Either<BaseError, Unit>>,
IEmbyBackgroundServiceRequest;

View File

@@ -0,0 +1,111 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Emby;
using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Application.Emby;
public class SynchronizeEmbyLibrariesHandler : IRequestHandler<SynchronizeEmbyLibraries, Either<BaseError, Unit>>
{
private readonly IEmbyApiClient _embyApiClient;
private readonly IEmbySecretStore _embySecretStore;
private readonly ILogger<SynchronizeEmbyLibrariesHandler> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly ISearchIndex _searchIndex;
public SynchronizeEmbyLibrariesHandler(
IMediaSourceRepository mediaSourceRepository,
IEmbySecretStore embySecretStore,
IEmbyApiClient embyApiClient,
ILogger<SynchronizeEmbyLibrariesHandler> logger,
ISearchIndex searchIndex)
{
_mediaSourceRepository = mediaSourceRepository;
_embySecretStore = embySecretStore;
_embyApiClient = embyApiClient;
_logger = logger;
_searchIndex = searchIndex;
}
public Task<Either<BaseError, Unit>> Handle(
SynchronizeEmbyLibraries request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(SynchronizeLibraries)
.Bind(v => v.ToEitherAsync());
private Task<Validation<BaseError, ConnectionParameters>> Validate(SynchronizeEmbyLibraries request) =>
MediaSourceMustExist(request)
.BindT(MediaSourceMustHaveActiveConnection)
.BindT(MediaSourceMustHaveApiKey);
private Task<Validation<BaseError, EmbyMediaSource>> MediaSourceMustExist(
SynchronizeEmbyLibraries request) =>
_mediaSourceRepository.GetEmby(request.EmbyMediaSourceId)
.Map(o => o.ToValidation<BaseError>("Emby media source does not exist."));
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
EmbyMediaSource embyMediaSource)
{
Option<EmbyConnection> maybeConnection = embyMediaSource.Connections.HeadOrNone();
return maybeConnection.Map(connection => new ConnectionParameters(embyMediaSource, connection))
.ToValidation<BaseError>("Emby media source requires an active connection");
}
private async Task<Validation<BaseError, ConnectionParameters>> MediaSourceMustHaveApiKey(
ConnectionParameters connectionParameters)
{
EmbySecrets secrets = await _embySecretStore.ReadSecrets();
return Optional(secrets.Address == connectionParameters.ActiveConnection.Address)
.Where(match => match)
.Map(_ => connectionParameters with { ApiKey = secrets.ApiKey })
.ToValidation<BaseError>("Emby media source requires an api key");
}
private async Task<Unit> SynchronizeLibraries(ConnectionParameters connectionParameters)
{
Either<BaseError, List<EmbyLibrary>> maybeLibraries = await _embyApiClient.GetLibraries(
connectionParameters.ActiveConnection.Address,
connectionParameters.ApiKey);
foreach (BaseError error in maybeLibraries.LeftToSeq())
{
_logger.LogWarning(
"Unable to synchronize libraries from emby server {EmbyServer}: {Error}",
connectionParameters.EmbyMediaSource.ServerName,
error.Value);
}
foreach (List<EmbyLibrary> libraries in maybeLibraries.RightToSeq())
{
var existing = connectionParameters.EmbyMediaSource.Libraries.OfType<EmbyLibrary>()
.ToList();
var toAdd = libraries.Filter(library => existing.All(l => l.ItemId != library.ItemId)).ToList();
var toRemove = existing.Filter(library => libraries.All(l => l.ItemId != library.ItemId)).ToList();
var toUpdate = libraries
.Filter(l => toAdd.All(a => a.ItemId != l.ItemId) && toRemove.All(r => r.ItemId != l.ItemId)).ToList();
List<int> ids = await _mediaSourceRepository.UpdateLibraries(
connectionParameters.EmbyMediaSource.Id,
toAdd,
toRemove,
toUpdate);
if (ids.Any())
{
await _searchIndex.RemoveItems(ids);
_searchIndex.Commit();
}
}
return Unit.Default;
}
private record ConnectionParameters(
EmbyMediaSource EmbyMediaSource,
EmbyConnection ActiveConnection)
{
public string ApiKey { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Emby;
public interface ISynchronizeEmbyLibraryById : IRequest<Either<BaseError, string>>,
IEmbyBackgroundServiceRequest
{
int EmbyLibraryId { get; }
bool ForceScan { get; }
}
public record SynchronizeEmbyLibraryByIdIfNeeded(int EmbyLibraryId) : ISynchronizeEmbyLibraryById
{
public bool ForceScan => false;
}
public record ForceSynchronizeEmbyLibraryById(int EmbyLibraryId) : ISynchronizeEmbyLibraryById
{
public bool ForceScan => true;
}

View File

@@ -0,0 +1,207 @@
using System.Threading.Channels;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Emby;
using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Locking;
using ErsatzTV.Core.Interfaces.Repositories;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Application.Emby;
public class SynchronizeEmbyLibraryByIdHandler :
IRequestHandler<ForceSynchronizeEmbyLibraryById, Either<BaseError, string>>,
IRequestHandler<SynchronizeEmbyLibraryByIdIfNeeded, Either<BaseError, string>>
{
private readonly IConfigElementRepository _configElementRepository;
private readonly IEmbyMovieLibraryScanner _embyMovieLibraryScanner;
private readonly IEmbySecretStore _embySecretStore;
private readonly IEmbyTelevisionLibraryScanner _embyTelevisionLibraryScanner;
private readonly ChannelWriter<IEmbyBackgroundServiceRequest> _embyWorkerChannel;
private readonly IEntityLocker _entityLocker;
private readonly ILibraryRepository _libraryRepository;
private readonly ILogger<SynchronizeEmbyLibraryByIdHandler> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository;
public SynchronizeEmbyLibraryByIdHandler(
IMediaSourceRepository mediaSourceRepository,
IEmbySecretStore embySecretStore,
IEmbyMovieLibraryScanner embyMovieLibraryScanner,
IEmbyTelevisionLibraryScanner embyTelevisionLibraryScanner,
ILibraryRepository libraryRepository,
IEntityLocker entityLocker,
IConfigElementRepository configElementRepository,
ChannelWriter<IEmbyBackgroundServiceRequest> embyWorkerChannel,
ILogger<SynchronizeEmbyLibraryByIdHandler> logger)
{
_mediaSourceRepository = mediaSourceRepository;
_embySecretStore = embySecretStore;
_embyMovieLibraryScanner = embyMovieLibraryScanner;
_embyTelevisionLibraryScanner = embyTelevisionLibraryScanner;
_libraryRepository = libraryRepository;
_entityLocker = entityLocker;
_configElementRepository = configElementRepository;
_embyWorkerChannel = embyWorkerChannel;
_logger = logger;
}
public Task<Either<BaseError, string>> Handle(
ForceSynchronizeEmbyLibraryById request,
CancellationToken cancellationToken) => HandleImpl(request, cancellationToken);
public Task<Either<BaseError, string>> Handle(
SynchronizeEmbyLibraryByIdIfNeeded request,
CancellationToken cancellationToken) => HandleImpl(request, cancellationToken);
private async Task<Either<BaseError, string>>
HandleImpl(ISynchronizeEmbyLibraryById request, CancellationToken cancellationToken)
{
Validation<BaseError, RequestParameters> validation = await Validate(request);
return await validation.Match(
parameters => Synchronize(parameters, cancellationToken),
error => Task.FromResult<Either<BaseError, string>>(error.Join()));
}
private async Task<Either<BaseError, string>> Synchronize(
RequestParameters parameters,
CancellationToken cancellationToken)
{
try
{
var lastScan = new DateTimeOffset(parameters.Library.LastScan ?? SystemTime.MinValueUtc, TimeSpan.Zero);
DateTimeOffset nextScan = lastScan + TimeSpan.FromHours(parameters.LibraryRefreshInterval);
if (parameters.ForceScan || nextScan < DateTimeOffset.Now)
{
Either<BaseError, Unit> result = parameters.Library.MediaKind switch
{
LibraryMediaKind.Movies =>
await _embyMovieLibraryScanner.ScanLibrary(
parameters.ConnectionParameters.ActiveConnection.Address,
parameters.ConnectionParameters.ApiKey,
parameters.Library,
parameters.FFmpegPath,
parameters.FFprobePath,
cancellationToken),
LibraryMediaKind.Shows =>
await _embyTelevisionLibraryScanner.ScanLibrary(
parameters.ConnectionParameters.ActiveConnection.Address,
parameters.ConnectionParameters.ApiKey,
parameters.Library,
parameters.FFmpegPath,
parameters.FFprobePath,
cancellationToken),
_ => Unit.Default
};
if (result.IsRight)
{
parameters.Library.LastScan = DateTime.UtcNow;
await _libraryRepository.UpdateLastScan(parameters.Library);
await _embyWorkerChannel.WriteAsync(
new SynchronizeEmbyCollections(parameters.Library.MediaSourceId),
cancellationToken);
}
return result.Map(_ => parameters.Library.Name);
}
else
{
_logger.LogDebug("Skipping unforced scan of emby media library {Name}", parameters.Library.Name);
}
return parameters.Library.Name;
}
finally
{
_entityLocker.UnlockLibrary(parameters.Library.Id);
}
}
private async Task<Validation<BaseError, RequestParameters>> Validate(
ISynchronizeEmbyLibraryById request) =>
(await ValidateConnection(request), await EmbyLibraryMustExist(request),
await ValidateLibraryRefreshInterval(), await ValidateFFmpegPath(), await ValidateFFprobePath())
.Apply(
(connectionParameters, embyLibrary, libraryRefreshInterval, ffmpegPath, ffprobePath) =>
new RequestParameters(
connectionParameters,
embyLibrary,
request.ForceScan,
libraryRefreshInterval,
ffmpegPath,
ffprobePath
));
private Task<Validation<BaseError, ConnectionParameters>> ValidateConnection(
ISynchronizeEmbyLibraryById request) =>
EmbyMediaSourceMustExist(request)
.BindT(MediaSourceMustHaveActiveConnection)
.BindT(MediaSourceMustHaveApiKey);
private Task<Validation<BaseError, EmbyMediaSource>> EmbyMediaSourceMustExist(
ISynchronizeEmbyLibraryById request) =>
_mediaSourceRepository.GetEmbyByLibraryId(request.EmbyLibraryId)
.Map(
v => v.ToValidation<BaseError>(
$"Emby media source for library {request.EmbyLibraryId} does not exist."));
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
EmbyMediaSource embyMediaSource)
{
Option<EmbyConnection> maybeConnection = embyMediaSource.Connections.HeadOrNone();
return maybeConnection.Map(connection => new ConnectionParameters(embyMediaSource, connection))
.ToValidation<BaseError>("Emby media source requires an active connection");
}
private async Task<Validation<BaseError, ConnectionParameters>> MediaSourceMustHaveApiKey(
ConnectionParameters connectionParameters)
{
EmbySecrets secrets = await _embySecretStore.ReadSecrets();
return Optional(secrets.Address == connectionParameters.ActiveConnection.Address)
.Where(match => match)
.Map(_ => connectionParameters with { ApiKey = secrets.ApiKey })
.ToValidation<BaseError>("Emby media source requires an api key");
}
private Task<Validation<BaseError, EmbyLibrary>> EmbyLibraryMustExist(
ISynchronizeEmbyLibraryById request) =>
_mediaSourceRepository.GetEmbyLibrary(request.EmbyLibraryId)
.Map(v => v.ToValidation<BaseError>($"Emby library {request.EmbyLibraryId} does not exist."));
private Task<Validation<BaseError, int>> ValidateLibraryRefreshInterval() =>
_configElementRepository.GetValue<int>(ConfigElementKey.LibraryRefreshInterval)
.FilterT(lri => lri > 0)
.Map(lri => lri.ToValidation<BaseError>("Library refresh interval is invalid"));
private Task<Validation<BaseError, string>> ValidateFFmpegPath() =>
_configElementRepository.GetValue<string>(ConfigElementKey.FFmpegPath)
.FilterT(File.Exists)
.Map(
ffmpegPath =>
ffmpegPath.ToValidation<BaseError>("FFmpeg path does not exist on the file system"));
private Task<Validation<BaseError, string>> ValidateFFprobePath() =>
_configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath)
.FilterT(File.Exists)
.Map(
ffprobePath =>
ffprobePath.ToValidation<BaseError>("FFprobe path does not exist on the file system"));
private record RequestParameters(
ConnectionParameters ConnectionParameters,
EmbyLibrary Library,
bool ForceScan,
int LibraryRefreshInterval,
string FFmpegPath,
string FFprobePath);
private record ConnectionParameters(
EmbyMediaSource EmbyMediaSource,
EmbyConnection ActiveConnection)
{
public string ApiKey { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Emby;
public record SynchronizeEmbyMediaSources : IRequest<Either<BaseError, List<EmbyMediaSource>>>,
IEmbyBackgroundServiceRequest;

View File

@@ -0,0 +1,35 @@
using System.Threading.Channels;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Emby;
public class SynchronizeEmbyMediaSourcesHandler : IRequestHandler<SynchronizeEmbyMediaSources,
Either<BaseError, List<EmbyMediaSource>>>
{
private readonly ChannelWriter<IEmbyBackgroundServiceRequest> _channel;
private readonly IMediaSourceRepository _mediaSourceRepository;
public SynchronizeEmbyMediaSourcesHandler(
IMediaSourceRepository mediaSourceRepository,
ChannelWriter<IEmbyBackgroundServiceRequest> channel)
{
_mediaSourceRepository = mediaSourceRepository;
_channel = channel;
}
public async Task<Either<BaseError, List<EmbyMediaSource>>> Handle(
SynchronizeEmbyMediaSources request,
CancellationToken cancellationToken)
{
List<EmbyMediaSource> mediaSources = await _mediaSourceRepository.GetAllEmby();
foreach (EmbyMediaSource mediaSource in mediaSources)
{
// await _channel.WriteAsync(new SynchronizeEmbyAdminUserId(mediaSource.Id), cancellationToken);
await _channel.WriteAsync(new SynchronizeEmbyLibraries(mediaSource.Id), cancellationToken);
}
return mediaSources;
}
}

View File

@@ -0,0 +1,8 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Emby;
public record UpdateEmbyLibraryPreferences
(List<EmbyLibraryPreference> Preferences) : IRequest<Either<BaseError, Unit>>;
public record EmbyLibraryPreference(int Id, bool ShouldSyncItems);

View File

@@ -0,0 +1,36 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
namespace ErsatzTV.Application.Emby;
public class
UpdateEmbyLibraryPreferencesHandler : IRequestHandler<UpdateEmbyLibraryPreferences,
Either<BaseError, Unit>>
{
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly ISearchIndex _searchIndex;
public UpdateEmbyLibraryPreferencesHandler(
IMediaSourceRepository mediaSourceRepository,
ISearchIndex searchIndex)
{
_mediaSourceRepository = mediaSourceRepository;
_searchIndex = searchIndex;
}
public async Task<Either<BaseError, Unit>> Handle(
UpdateEmbyLibraryPreferences request,
CancellationToken cancellationToken)
{
var toDisable = request.Preferences.Filter(p => p.ShouldSyncItems == false).Map(p => p.Id).ToList();
List<int> ids = await _mediaSourceRepository.DisableEmbyLibrarySync(toDisable);
await _searchIndex.RemoveItems(ids);
_searchIndex.Commit();
IEnumerable<int> toEnable = request.Preferences.Filter(p => p.ShouldSyncItems).Map(p => p.Id);
await _mediaSourceRepository.EnableEmbyLibrarySync(toEnable);
return Unit.Default;
}
}

View File

@@ -0,0 +1,9 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Emby;
public record UpdateEmbyPathReplacements(
int EmbyMediaSourceId,
List<EmbyPathReplacementItem> PathReplacements) : IRequest<Either<BaseError, Unit>>;
public record EmbyPathReplacementItem(int Id, string EmbyPath, string LocalPath);

View File

@@ -0,0 +1,49 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Emby;
public class UpdateEmbyPathReplacementsHandler : IRequestHandler<UpdateEmbyPathReplacements,
Either<BaseError, Unit>>
{
private readonly IMediaSourceRepository _mediaSourceRepository;
public UpdateEmbyPathReplacementsHandler(IMediaSourceRepository mediaSourceRepository) =>
_mediaSourceRepository = mediaSourceRepository;
public Task<Either<BaseError, Unit>> Handle(
UpdateEmbyPathReplacements request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(pms => MergePathReplacements(request, pms))
.Bind(v => v.ToEitherAsync());
private Task<Unit> MergePathReplacements(
UpdateEmbyPathReplacements request,
EmbyMediaSource embyMediaSource)
{
embyMediaSource.PathReplacements ??= new List<EmbyPathReplacement>();
var incoming = request.PathReplacements.Map(Project).ToList();
var toAdd = incoming.Filter(r => r.Id < 1).ToList();
var toRemove = embyMediaSource.PathReplacements.Filter(r => incoming.All(pr => pr.Id != r.Id)).ToList();
var toUpdate = incoming.Except(toAdd).ToList();
return _mediaSourceRepository.UpdatePathReplacements(embyMediaSource.Id, toAdd, toUpdate, toRemove);
}
private static EmbyPathReplacement Project(EmbyPathReplacementItem vm) =>
new() { Id = vm.Id, EmbyPath = vm.EmbyPath, LocalPath = vm.LocalPath };
private Task<Validation<BaseError, EmbyMediaSource>> Validate(UpdateEmbyPathReplacements request) =>
EmbyMediaSourceMustExist(request);
private Task<Validation<BaseError, EmbyMediaSource>> EmbyMediaSourceMustExist(
UpdateEmbyPathReplacements request) =>
_mediaSourceRepository.GetEmby(request.EmbyMediaSourceId)
.Map(
v => v.ToValidation<BaseError>(
$"Emby media source {request.EmbyMediaSourceId} does not exist."));
}

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Emby;
public record EmbyConnectionParametersViewModel(string Address);

View File

@@ -0,0 +1,12 @@
using ErsatzTV.Application.Libraries;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Emby;
public record EmbyLibraryViewModel(
int Id,
string Name,
LibraryMediaKind MediaKind,
bool ShouldSyncItems,
int MediaSourceId)
: LibraryViewModel("Emby", Id, Name, MediaKind, MediaSourceId);

View File

@@ -0,0 +1,8 @@
using ErsatzTV.Application.MediaSources;
namespace ErsatzTV.Application.Emby;
public record EmbyMediaSourceViewModel(int Id, string Name, string Address) : RemoteMediaSourceViewModel(
Id,
Name,
Address);

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Emby;
public record EmbyPathReplacementViewModel(int Id, string EmbyPath, string LocalPath);

View File

@@ -0,0 +1,18 @@
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Emby;
internal static class Mapper
{
internal static EmbyMediaSourceViewModel ProjectToViewModel(EmbyMediaSource embyMediaSource) =>
new(
embyMediaSource.Id,
embyMediaSource.ServerName,
embyMediaSource.Connections.HeadOrNone().Match(c => c.Address, string.Empty));
internal static EmbyLibraryViewModel ProjectToViewModel(EmbyLibrary library) =>
new(library.Id, library.Name, library.MediaKind, library.ShouldSyncItems, library.MediaSourceId);
internal static EmbyPathReplacementViewModel ProjectToViewModel(EmbyPathReplacement pathReplacement) =>
new(pathReplacement.Id, pathReplacement.EmbyPath, pathReplacement.LocalPath);
}

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Emby;
public record GetAllEmbyMediaSources : IRequest<List<EmbyMediaSourceViewModel>>;

View File

@@ -0,0 +1,17 @@
using ErsatzTV.Core.Interfaces.Repositories;
using static ErsatzTV.Application.Emby.Mapper;
namespace ErsatzTV.Application.Emby;
public class GetAllEmbyMediaSourcesHandler : IRequestHandler<GetAllEmbyMediaSources, List<EmbyMediaSourceViewModel>>
{
private readonly IMediaSourceRepository _mediaSourceRepository;
public GetAllEmbyMediaSourcesHandler(IMediaSourceRepository mediaSourceRepository) =>
_mediaSourceRepository = mediaSourceRepository;
public Task<List<EmbyMediaSourceViewModel>> Handle(
GetAllEmbyMediaSources request,
CancellationToken cancellationToken) =>
_mediaSourceRepository.GetAllEmby().Map(list => list.Map(ProjectToViewModel).ToList());
}

View File

@@ -0,0 +1,5 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.Emby;
public record GetEmbyConnectionParameters : IRequest<Either<BaseError, EmbyConnectionParametersViewModel>>;

View File

@@ -0,0 +1,66 @@
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using Microsoft.Extensions.Caching.Memory;
namespace ErsatzTV.Application.Emby;
public class GetEmbyConnectionParametersHandler : IRequestHandler<GetEmbyConnectionParameters,
Either<BaseError, EmbyConnectionParametersViewModel>>
{
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IMemoryCache _memoryCache;
public GetEmbyConnectionParametersHandler(
IMemoryCache memoryCache,
IMediaSourceRepository mediaSourceRepository)
{
_memoryCache = memoryCache;
_mediaSourceRepository = mediaSourceRepository;
}
public async Task<Either<BaseError, EmbyConnectionParametersViewModel>> Handle(
GetEmbyConnectionParameters request,
CancellationToken cancellationToken)
{
if (_memoryCache.TryGetValue(request, out EmbyConnectionParametersViewModel parameters))
{
return parameters;
}
Either<BaseError, EmbyConnectionParametersViewModel> maybeParameters =
await Validate()
.MapT(cp => new EmbyConnectionParametersViewModel(cp.ActiveConnection.Address))
.Map(v => v.ToEither<EmbyConnectionParametersViewModel>());
return maybeParameters.Match(
p =>
{
_memoryCache.Set(request, p, TimeSpan.FromHours(1));
return maybeParameters;
},
error => error);
}
private Task<Validation<BaseError, ConnectionParameters>> Validate() =>
EmbyMediaSourceMustExist()
.BindT(MediaSourceMustHaveActiveConnection);
private Task<Validation<BaseError, EmbyMediaSource>> EmbyMediaSourceMustExist() =>
_mediaSourceRepository.GetAllEmby().Map(list => list.HeadOrNone())
.Map(
v => v.ToValidation<BaseError>(
"Emby media source does not exist."));
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
EmbyMediaSource embyMediaSource)
{
Option<EmbyConnection> maybeConnection = embyMediaSource.Connections.FirstOrDefault();
return maybeConnection.Map(connection => new ConnectionParameters(embyMediaSource, connection))
.ToValidation<BaseError>("Emby media source requires an active connection");
}
private record ConnectionParameters(
EmbyMediaSource EmbyMediaSource,
EmbyConnection ActiveConnection);
}

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Emby;
public record GetEmbyLibrariesBySourceId(int EmbyMediaSourceId) : IRequest<List<EmbyLibraryViewModel>>;

View File

@@ -0,0 +1,19 @@
using ErsatzTV.Core.Interfaces.Repositories;
using static ErsatzTV.Application.Emby.Mapper;
namespace ErsatzTV.Application.Emby;
public class
GetEmbyLibrariesBySourceIdHandler : IRequestHandler<GetEmbyLibrariesBySourceId, List<EmbyLibraryViewModel>>
{
private readonly IMediaSourceRepository _mediaSourceRepository;
public GetEmbyLibrariesBySourceIdHandler(IMediaSourceRepository mediaSourceRepository) =>
_mediaSourceRepository = mediaSourceRepository;
public Task<List<EmbyLibraryViewModel>> Handle(
GetEmbyLibrariesBySourceId request,
CancellationToken cancellationToken) =>
_mediaSourceRepository.GetEmbyLibraries(request.EmbyMediaSourceId)
.Map(list => list.Map(ProjectToViewModel).ToList());
}

View File

@@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Emby;
public record GetEmbyMediaSourceById(int EmbyMediaSourceId) : IRequest<Option<EmbyMediaSourceViewModel>>;

View File

@@ -0,0 +1,18 @@
using ErsatzTV.Core.Interfaces.Repositories;
using static ErsatzTV.Application.Emby.Mapper;
namespace ErsatzTV.Application.Emby;
public class
GetEmbyMediaSourceByIdHandler : IRequestHandler<GetEmbyMediaSourceById, Option<EmbyMediaSourceViewModel>>
{
private readonly IMediaSourceRepository _mediaSourceRepository;
public GetEmbyMediaSourceByIdHandler(IMediaSourceRepository mediaSourceRepository) =>
_mediaSourceRepository = mediaSourceRepository;
public Task<Option<EmbyMediaSourceViewModel>> Handle(
GetEmbyMediaSourceById request,
CancellationToken cancellationToken) =>
_mediaSourceRepository.GetEmby(request.EmbyMediaSourceId).MapT(ProjectToViewModel);
}

View File

@@ -0,0 +1,4 @@
namespace ErsatzTV.Application.Emby;
public record GetEmbyPathReplacementsBySourceId
(int EmbyMediaSourceId) : IRequest<List<EmbyPathReplacementViewModel>>;

View File

@@ -0,0 +1,19 @@
using ErsatzTV.Core.Interfaces.Repositories;
using static ErsatzTV.Application.Emby.Mapper;
namespace ErsatzTV.Application.Emby;
public class GetEmbyPathReplacementsBySourceIdHandler : IRequestHandler<GetEmbyPathReplacementsBySourceId,
List<EmbyPathReplacementViewModel>>
{
private readonly IMediaSourceRepository _mediaSourceRepository;
public GetEmbyPathReplacementsBySourceIdHandler(IMediaSourceRepository mediaSourceRepository) =>
_mediaSourceRepository = mediaSourceRepository;
public Task<List<EmbyPathReplacementViewModel>> Handle(
GetEmbyPathReplacementsBySourceId request,
CancellationToken cancellationToken) =>
_mediaSourceRepository.GetEmbyPathReplacements(request.EmbyMediaSourceId)
.Map(list => list.Map(ProjectToViewModel).ToList());
}

Some files were not shown because too many files have changed in this diff Show More