Compare commits

...

291 Commits

Author SHA1 Message Date
Jason Dove
5237e6fa50 update changelog for release v0.7.2-beta [no ci] 2023-01-05 11:51:57 -06:00
Jason Dove
99bde1819c use mknejp/delete-release-assets again (#1098) 2023-01-05 10:26:42 -06:00
Jason Dove
f5d7ec2890 update workflow [no ci] 2023-01-05 10:06:32 -06:00
Jason Dove
13c65435d3 update dependencies (#1097) 2023-01-05 09:27:12 -06:00
Jason Dove
315420f1a5 fix log viewer on windows (#1095)
* fix log viewer on windows

* catch cancellation on trakt page

* update changelog
2023-01-04 22:26:35 -06:00
Jason Dove
ab7051f075 reimplement log viewer (#1094) 2023-01-04 10:09:11 -06:00
Jason Dove
a43e5bbe9d update changelog for release v0.7.1-beta [no ci] 2023-01-03 09:40:59 -06:00
Jason Dove
b7bd4541b1 hide windows on windows (#1091)
* hide windows on windows

* update dependencies
2023-01-03 09:14:50 -06:00
Jason Dove
648f25e9cc fix terminating server process 2023-01-01 14:20:52 -06:00
Jason Dove
ccbe85a46a also hide the main server window 2023-01-01 14:12:41 -06:00
Jason Dove
d168d79fe0 hide windows wrapper console (#1088) 2023-01-01 13:42:39 -06:00
Jason Dove
d37dde2477 try to fix windows build 2023-01-01 13:19:12 -06:00
Jason Dove
8e13b07c84 convert windows project from dotnet to rust (#1087)
* convert windows project from dotnet to rust

* update pr jobs

* pr job fixes

* don't bother building mac app in prs for now

* build windows wrapper with rust
2023-01-01 13:01:58 -06:00
Jason Dove
927e7724f0 fix search bug (#1086)
* fix removing media items from search index

* update changelog
2023-01-01 08:49:15 -06:00
Jason Dove
6558c5bd69 fix subtitle update bug (#1085)
* fix saving some subtitles to database

* fix ffprobe regression
2022-12-31 19:57:14 -06:00
Jason Dove
5f7efbb69c properly fall back to software pipeline (#1084) 2022-12-31 14:06:00 -06:00
Jason Dove
b79795af50 add debug logging to local subtitle provider (#1083) 2022-12-31 11:36:08 -06:00
Jason Dove
9479806cb0 scanner refactoring and other cleanup (#1082)
* move subtitles provider into scanner

* move more stuff into scanner

* move nfo into scanner

* add scan subcommand

* fix a bunch of nfo build warnings

* more subcommands

* fix warnings

* cleanup logging

* remove unused code

* cleanup old ffmpeg stuff

* rename complex filter

* refactor wrapped segmenter
2022-12-31 10:57:20 -06:00
Jason Dove
6e49ea78ec music video template contrib (#1081)
* add another music video template

* add more music video credit templates
2022-12-30 13:26:07 -06:00
Jason Dove
7b1edd9c54 add new scanner process (#1080)
* start moving local scans to separate process

* send progress updates to main process

* move scanners and tests

* simplify dependencies; sync search index

* commit search index more often when scanning

* support forced scan and cancellation

* use scanner process for plex libraries

* update changelog

* update dockerfiles

* fix search index for local folder scanning

* rework plex scanners

* rework scanner handlers

* emby works again

* sync jellyfin

* cleanup

* update build

* update changelog

* remove scanner dependency in pr and artifacts workflows

* fix mac sed syntax

* fix pr build
2022-12-30 12:53:05 -06:00
Jason Dove
aeaafd2964 add scanner subtitle logging (#1079) 2022-12-29 06:00:00 -06:00
Jason Dove
622fa01602 update dependencies and fix some types (#1077) 2022-12-28 14:21:25 -06:00
Jason Dove
e2b3c1ce8e properly unflag local movies that are now present on disk (#1076) 2022-12-28 13:41:01 -06:00
Jason Dove
6c5db650e7 nvidia pixel format and song fixes (#1075)
* fix nvidia pixel format edge case

* fix nvidia song playback
2022-12-24 20:39:22 -06:00
Jason Dove
731072425b fix nvidia pipeline that only requires setparams (#1074) 2022-12-24 13:19:27 -06:00
Jason Dove
0f817308a8 limit library scan interval (#1073)
* limit library scan interval

* fix condition
2022-12-24 12:58:38 -06:00
Jason Dove
0fc1e15cac colorspace fixes; song playback fixes (#1072)
* fix colorspace bug, vaapi song playback

* more colorspace fixes, nvidia fixes

* nvidia colorspace fixes

* fix some qsv output color metadata

* update changelog

* update changelog
2022-12-23 15:11:05 -06:00
Jason Dove
acf30384b7 update changelog [no ci] 2022-12-20 20:14:31 -06:00
Jason Dove
d2040eaac9 pipeline fixes when colorspace filter is used (#1068)
* fix colorspace filter with missing input transfer or input primaries

* properly download before applying colorspace filter

* fix extra hwupload/hwdownload with nvidia pipeline

* colorspace tests

* update dependencies
2022-12-20 20:12:27 -06:00
Jason Dove
93673fce03 add more logging to vaapi capabilities detection (#1059) 2022-12-15 19:32:48 -06:00
Jason Dove
d7a432068b fix arm docker builds 2022-12-15 14:26:00 -06:00
Jason Dove
cb9215980a fix dockerfiles, focal to jammy 2022-12-15 14:14:07 -06:00
Jason Dove
a4fc1f1c6f upgrade to dotnet 7, ffmpeg 5.1.2 (#1058)
* wip

* update dockerfiles

* more net6 to net7

* update dependencies

* update builds
2022-12-15 14:08:21 -06:00
Jason Dove
cbbdb11938 update changelog for release v0.7.0-beta [no ci] 2022-12-11 06:53:05 -06:00
Jason Dove
a2274bca7b detect vaapi capabilities (#1051)
* remove unused pipeline

* spike vaapi hardware capabilities

* more vaapi capabilities

* use proper vaapi driver

* update readme

* update dependencies
2022-12-10 14:10:19 -06:00
Jason Dove
f84496b09d extract attached fonts (#1050) 2022-12-09 22:22:15 -06:00
Jason Dove
3abf310a3b add amf pipeline (#1048) 2022-12-09 15:23:00 -06:00
Jason Dove
f12e361c2e fix videotoolbox color normalization (#1047) 2022-12-08 13:11:46 -06:00
Jason Dove
cd0f1e98cc fix qsv color normalization (#1046) 2022-12-08 08:17:59 -06:00
Jason Dove
325ef80951 normalize bit depth via new pipeline (#1045)
* fix subtitles test and nvidia subtitles

* new ffmpeg pipelines; software and nvidia

* partial qsv

* fix qsv

* fix software pipeline

* add vaapi pipeline

* fix qsv 10-bit h264 output

* nvidia fixes

* properly disable 10-bit h264 hardware encoders

* more nvidia fixes

* add video toolbox pipeline
2022-12-07 21:25:55 -06:00
Jason Dove
9a30d7c7da error report bug fixes (#1042)
* fix some potential null reference exceptions

* searching isn't actually async

* add search query breadcrumb
2022-12-03 05:47:04 -06:00
Jason Dove
25ea75b761 more color fixes (#1040) 2022-11-25 21:25:04 -06:00
Jason Dove
32edf77d35 fix bt709 check (#1039) 2022-11-25 10:09:58 -06:00
Jason Dove
47fbb2b1b7 properly unlock trakt (#1035) 2022-11-23 18:34:55 -06:00
Jason Dove
e388f81e1f re-enable bugsnag auto notification (#1034) 2022-11-22 20:09:25 -06:00
Jason Dove
f0bea295c4 add video stats to search index (#1033) 2022-11-22 09:35:28 -06:00
Jason Dove
7439ded43d normalize bit depth (#1031)
* normalize bit depth and color for nvenc

* fix hls direct

* update changelog

* add bit depth option to ffmpeg profile
2022-11-21 20:20:07 -06:00
Jason Dove
6a640d3708 fix ogg song metadata (#1030) 2022-11-20 12:44:08 -06:00
Jason Dove
776bce9087 use base path in channel playlist and channel guide (#1028) 2022-11-20 08:28:14 -06:00
Jason Dove
3c499f9e97 proper fix 2022-11-19 10:41:16 -06:00
Jason Dove
114ff7a3e3 fix develop build cleanup (#1027) 2022-11-19 10:16:12 -06:00
Jason Dove
527cdf523c fix work-ahead limit setting (#1023) 2022-11-16 21:17:23 -06:00
Jason Dove
91eb8ab824 try to fix develop release 2022-11-04 11:26:05 -05:00
Jason Dove
7a87fb1c2e fix removing emby and jellyfin libraries (#1011)
* fix removing jellyfin and emby libraries

* remove unneeded change
2022-11-04 06:25:51 -05:00
Jason Dove
d8cc6b4c22 fix audio stream selection (#1010) 2022-11-02 14:55:43 -05:00
Jason Dove
c9bd94d9f8 use javascript instead of lua for external scripts; add audio stream selector scripts (#1005)
* use js instead of lua

* update dependencies

* add audio stream selector script for episodes

* add audio stream selector script for movies

* update changelog
2022-10-28 17:05:07 -05:00
Jason Dove
93bf818882 fix syntax [no ci] 2022-10-22 11:20:42 -05:00
Jason Dove
723fb3848d try to fix release by skipping unnecessary step 2022-10-22 11:19:39 -05:00
Jason Dove
6a213e2249 update changelog for release v0.6.9-beta [no ci] 2022-10-21 21:15:43 -05:00
Jason Dove
a6c5c3a317 bump search index version 2022-10-21 15:21:33 -05:00
Jason Dove
9313d2c8eb fix guide mode filler in xmltv (#1000) 2022-10-16 13:23:26 -05:00
Jason Dove
485a874ab5 fix automatic playout reset (#999) 2022-10-16 10:30:54 -05:00
Jason Dove
f2bc884632 fix x-forwarded-proto (#998) 2022-10-13 13:16:51 -05:00
Jason Dove
39d6653f8e temporarily enable http logging (#997) 2022-10-13 12:58:15 -05:00
Jason Dove
2ce0fcb264 proxy server improvements (#996) 2022-10-13 12:17:13 -05:00
Jason Dove
8bf5e18ae5 fix nfo reader (#995)
* fix nfo reader

* fix template fade

* update dependencies
2022-10-13 05:08:41 -05:00
Jason Dove
88f4d8074a fix stream_seek type (#988)
* fix stream seek type

* cleanup
2022-10-09 21:20:34 -05:00
Jason Dove
f5aa2fcac8 add multi-episode shuffle playout order (#987) 2022-10-09 10:49:07 -05:00
Jason Dove
6f892bea6b optionally place watermark within source content (#986) 2022-10-09 08:16:59 -05:00
Jason Dove
cbf0c9c988 fix transcoding tests 2022-10-08 21:41:49 -05:00
Jason Dove
393c67213d add stream_seek to music video credits template (#985) 2022-10-08 19:45:24 -05:00
Jason Dove
f69de9f071 fix all_artists in music video credits template (#984) 2022-10-08 12:40:46 -05:00
Jason Dove
2e400c0d22 simplify music video credits config (#983) 2022-10-08 07:52:57 -05:00
Jason Dove
6035c10550 add music video credits template system (#982)
* add music video credits template system

* fix search index bug

* cleanup csproj
2022-10-07 21:02:29 -05:00
Jason Dove
555b156154 fix tail and fallback filler scheduling (#981) 2022-10-07 09:21:19 -05:00
Jason Dove
a0ea2e8910 update changelog for release v0.6.8-beta [no ci] 2022-10-05 11:09:03 -05:00
Jason Dove
734ca39cbd add guids to search index (#980) 2022-10-04 19:52:19 -05:00
Jason Dove
e0e5cfada5 fix numeric range search queries (#979) 2022-10-04 18:49:35 -05:00
Jason Dove
7e0c43bc46 update dependencies (#977) 2022-10-01 07:56:14 -05:00
Jason Dove
be1125a9ab properly sync updated file paths from plex (#976) 2022-09-30 20:41:32 -05:00
Jason Dove
d21c985a77 fix emby tag sync for movies and shows (#975) 2022-09-30 19:56:19 -05:00
Jason Dove
28f2b9b27e disable anamorphic edge case in scaling calculations (#971) 2022-09-26 15:11:05 -05:00
Jason Dove
9b185e19e9 fix other video search results when nfo metadata is used (#970)
* add debug no sync build config

* fix other video search results

* update dependencies
2022-09-25 22:27:34 -05:00
Jason Dove
27b923b462 qsv and vaapi scaling fixes (#966)
* add qsv device option to ffmpeg profile

* fix vaapi scaling

* cleanup
2022-09-18 21:02:09 -05:00
Jason Dove
357dfee050 nvidia and software mode scaling improvements (#965)
* convert to square pixels before software scaling

* convert to square pixels in nvidia scale filter

* more scaling fixes; position watermark within padded content

* fix image subtitle scaling

* fix qsv scaling

* update dependencies
2022-09-18 11:04:02 -05:00
Jason Dove
7f4004c228 fix qsv hevc encoder (#956)
* update dependencies

* fix typo in qsv hevc encoder param

* update changelog
2022-09-10 15:44:59 -05:00
Jason Dove
9b8dc0ed80 update changelog for release v0.6.7-beta [no ci] 2022-09-05 13:15:04 -05:00
Jason Dove
3cc1286271 include other videos (ungrouped) in shuffle in order (#953)
* include other videos (ungrouped) in shuffle in order

* fix id conflict
2022-09-05 09:20:11 -05:00
Jason Dove
df281758b7 properly fix infinite playout build loop (#952) 2022-09-05 08:41:39 -05:00
Jason Dove
25273c18c8 stop infinite playout building loop (#951) 2022-09-04 20:28:28 -05:00
Jason Dove
f1be945423 add qsv extra hardware frames setting (#950)
* wip add qsv extra_hw_frames setting

* fix ffmpeg profile editor

* update changelog
2022-09-04 18:07:03 -05:00
Jason Dove
9a4f772f53 fix image subtitle scaling (#949)
* properly scale image-based subtitles for nvidia and software

* fix vaapi image subtitle scaling

* fix qsv image subtitle scaling

* update changelog
2022-09-04 14:25:05 -05:00
Jason Dove
d669e8114b more scaling fixes (#948)
* remove force_original_aspect_ratio from scale_cuda

* remove force_original_aspect_ratio from scale_cuda

* fix qsv scaling

* fix qsv scaling on linux

* fix vaapi scaling edge cases

* update changelog
2022-09-03 20:28:18 -05:00
Jason Dove
3972e3603b add amf acceleration (#947) 2022-09-03 10:39:54 -05:00
dependabot[bot]
acc22fcb62 Bump MudBlazor from 6.0.14 to 6.0.15 (#945)
Bumps [MudBlazor](https://github.com/MudBlazor/MudBlazor) from 6.0.14 to 6.0.15.
- [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.14...v6.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-01 19:41:34 -05:00
Jason Dove
2df360d7fb fix xmltv filler bug (#944) 2022-08-31 20:15:25 -05:00
Jason Dove
46331ed2c6 add preferred audio title feature (#943)
* use consistent edit/delete icons

* add preferred audio title feature

* update dependencies
2022-08-30 17:04:41 -05:00
Jason Dove
3aee3b0515 fix windows build 2022-08-26 09:07:46 -05:00
Jason Dove
72c45692b2 update dependencies (#937) 2022-08-26 08:52:59 -05:00
Jason Dove
8edf71ca55 downgrade libva, include vainfo in docker (#936) 2022-08-25 16:29:20 -05:00
Jason Dove
612b9e6524 fix scanner crash caused by invalid mtime (#934) 2022-08-20 19:56:37 -05:00
Jason Dove
7aff65f07b explicitly copy all audio streams with hls direct (#933)
* ensure audio streams are always copied with hls direct

* update changelog
2022-08-18 14:23:51 -05:00
Jason Dove
5d350fcfad update changelog for release v0.6.6-beta [no ci] 2022-08-17 20:40:50 -05:00
Jason Dove
5546ad204c upgrade to ffmpeg 5.1 (#931)
* use ffmpeg 5.1 on windows

* remove some debug logs

* use latest ffmpeg on arm

* use ffmpeg 5.1 base images

* update ffmpeg health check for 5.1

* update changelog
2022-08-17 14:57:23 -05:00
Jason Dove
d66efa0a1d prioritize container aspect ratio over stream aspect ratio (#930)
* prioritize container aspect ratio over stream aspect ratio

* use setdar filter
2022-08-16 19:57:38 -05:00
Jason Dove
36d3d38530 remove all use of setsar filter (#928) 2022-08-16 12:25:59 -05:00
Jason Dove
8e79141860 use multi-variant playlists for hls segmenter (#926)
* use multi-variant playlists for hls segmenter

* use lowercase mime type
2022-08-13 19:58:08 -05:00
Jason Dove
9b3545f7ca add some temporary debug logging (#925) 2022-08-13 19:19:47 -05:00
Jason Dove
56db20faa0 limit segmenter delay to 8s (#924)
* always return initial hls playlist after 8 seconds

* update dependencies

* make fluentvalidation happy
2022-08-12 19:48:24 -05:00
Jason Dove
b0bd4c9fed add ogg file formats to local song library scanner (#914)
* add ogg file formats to local song library scanner

* update dependencies
2022-08-04 16:01:17 -05:00
Jason Dove
ba079452e2 add dff and dsf to local song library scanner (#911) 2022-08-03 11:01:13 -05:00
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
1701 changed files with 274012 additions and 18792 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
"version": "2021.2.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

View File

@@ -41,15 +41,25 @@ jobs:
target: osx-arm64
steps:
- name: Get the sources
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.0.x
dotnet-version: 7.0.x
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '14'
- name: Cache NPM dependencies
uses: bahmutov/npm-install@v1.8.28
with:
working-directory: ErsatzTV/client-app
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
@@ -71,7 +81,10 @@ jobs:
- 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
run: |
sed -i '' '/Scanner/d' ErsatzTV/ErsatzTV.csproj
dotnet publish ErsatzTV.Scanner/ErsatzTV.Scanner.csproj --framework net7.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
dotnet publish ErsatzTV/ErsatzTV.csproj --framework net7.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
@@ -98,6 +111,7 @@ jobs:
--icon "ErsatzTV.app" 200 190 \
--hide-extension "ErsatzTV.app" \
--app-drop-link 600 185 \
--skip-jenkins \
"ErsatzTV.dmg" \
"ErsatzTV.app/"
@@ -120,6 +134,7 @@ jobs:
- name: Delete old release assets
uses: mknejp/delete-release-assets@v1
if: ${{ inputs.release_tag == 'develop' }}
with:
token: ${{ secrets.gh_token }}
tag: ${{ inputs.release_tag }}
@@ -157,27 +172,43 @@ jobs:
target: win-x64
steps:
- name: Get the sources
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.0.x
dotnet-version: 7.0.x
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '14'
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
if: ${{ matrix.kind == 'windows' }}
- name: Cache NPM dependencies
uses: bahmutov/npm-install@v1.8.28
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}}"
run: dotnet restore -r "${{ matrix.target }}"
- uses: suisei-cn/actions-download-file@v1
if: ${{ matrix.kind }} == "windows"
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"
url: "https://github.com/GyanD/codexffmpeg/releases/download/5.1/ffmpeg-5.1-full_build.7z"
target: ffmpeg/
- name: Build
@@ -188,11 +219,15 @@ jobs:
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
sed -i '/Scanner/d' ErsatzTV/ErsatzTV.csproj
dotnet publish ErsatzTV.Scanner/ErsatzTV.Scanner.csproj --framework net7.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
dotnet publish ErsatzTV/ErsatzTV.csproj --framework net7.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
cargo build --manifest-path=ErsatzTV-Windows/Cargo.toml --release --all-features
ls -l ErsatzTV-Windows/target/release
mv ErsatzTV-Windows/target/release/ersatztv_windows.exe "$release_name/ErsatzTV-Windows.exe"
fi
# Download ffmpeg
@@ -216,6 +251,7 @@ jobs:
- name: Delete old release assets
uses: mknejp/delete-release-assets@v1
if: ${{ inputs.release_tag == 'develop' }}
with:
token: ${{ secrets.gh_token }}
tag: ${{ inputs.release_tag }}

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Get the sources
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Extract Docker Tag
@@ -19,14 +19,14 @@ jobs:
tag=$(git describe --tags --abbrev=0)
tag2="${tag:1}"
short=$(git rev-parse --short HEAD)
final="${tag2/alpha/$short}"
final="${tag2/beta/$short}"
echo "GIT_TAG=${final}" >> $GITHUB_ENV
- name: Extract Artifacts Version
shell: bash
run: |
tag=$(git describe --tags --abbrev=0)
short=$(git rev-parse --short HEAD)
final="${tag/alpha/$short}"
final="${tag/beta/$short}"
echo "ARTIFACTS_VERSION=${final}" >> $GITHUB_ENV
echo "INFO_VERSION=${tag:1}" >> $GITHUB_ENV
outputs:

View File

@@ -24,65 +24,89 @@ jobs:
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@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Docker Buildx Base
uses: docker/setup-buildx-action@v1
id: builder-base
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
if: ${{ matrix.qemu == true }}
- 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: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
id: docker-buildx
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.docker_hub_username }}
password: ${{ secrets.docker_hub_access_token }}
- name: Build and push base
uses: docker/build-push-action@v2
- name: Build and push
uses: docker/build-push-action@v3
with:
builder: ${{ steps.builder-base.outputs.name }}
builder: ${{ steps.docker-buildx.outputs.name }}
context: .
file: ./docker/Dockerfile
file: ./docker/${{ matrix.path }}Dockerfile
push: true
build-args: |
INFO_VERSION=${{ inputs.info_version }}-docker
INFO_VERSION=${{ inputs.info_version }}-docker${{ matrix.suffix }}
tags: |
jasongdove/ersatztv:${{ inputs.base_version }}
jasongdove/ersatztv:${{ inputs.tag_version }}
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 nvidia
uses: docker/build-push-action@v2
- name: Build and push
uses: docker/build-push-action@v3
with:
builder: ${{ steps.builder-nvidia.outputs.name }}
builder: ${{ steps.docker-buildx.outputs.name }}
context: .
file: ./docker/nvidia/Dockerfile
file: ./docker/${{ matrix.path }}Dockerfile
push: true
platforms: 'linux/arm64'
build-args: |
INFO_VERSION=${{ inputs.info_version }}-docker-nvidia
INFO_VERSION=${{ inputs.info_version }}-docker${{ matrix.suffix }}
tags: |
jasongdove/ersatztv:${{ inputs.base_version }}-nvidia
jasongdove/ersatztv:${{ inputs.tag_version }}-nvidia
jasongdove/ersatztv:${{ inputs.base_version }}${{ matrix.suffix }}
jasongdove/ersatztv:${{ inputs.tag_version }}${{ matrix.suffix }}
if: ${{ matrix.name == 'arm64' }}
- name: Build and push vaapi
uses: docker/build-push-action@v2
- name: Build and push
uses: docker/build-push-action@v3
with:
builder: ${{ steps.builder-vaapi.outputs.name }}
builder: ${{ steps.docker-buildx.outputs.name }}
context: .
file: ./docker/vaapi/Dockerfile
file: ./docker/${{ matrix.path }}Dockerfile
push: true
platforms: 'linux/arm/v7'
build-args: |
INFO_VERSION=${{ inputs.info_version }}-docker-vaapi
INFO_VERSION=${{ inputs.info_version }}-docker${{ matrix.suffix }}
tags: |
jasongdove/ersatztv:${{ inputs.base_version }}-vaapi
jasongdove/ersatztv:${{ inputs.tag_version }}-vaapi
jasongdove/ersatztv:${{ inputs.base_version }}${{ matrix.suffix }}
jasongdove/ersatztv:${{ inputs.tag_version }}${{ matrix.suffix }}
if: ${{ matrix.name == 'arm32v7' }}

View File

@@ -3,13 +3,16 @@ on:
push:
branches:
- main
paths:
- docs/**
- mkdocs.yml
jobs:
build:
name: Deploy docs
runs-on: ubuntu-latest
steps:
- name: Checkout master
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@master

View File

@@ -2,20 +2,21 @@
on:
pull_request:
jobs:
build_and_test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ windows-latest, ubuntu-latest, macos-latest ]
build_and_test_windows:
runs-on: windows-latest
steps:
- name: Get the sources
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.0.x
dotnet-version: 7.0.x
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
@@ -23,6 +24,67 @@ jobs:
- name: Install dependencies
run: dotnet restore
- name: Prep project file
run: sed -i '/Scanner/d' ErsatzTV/ErsatzTV.csproj
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --no-restore --verbosity normal
- name: Build Windows
run: |
cd ErsatzTV-Windows
cargo build --release --all-features
build_and_test_linux:
runs-on: ubuntu-latest
steps:
- name: Get the sources
uses: actions/checkout@v3
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
- name: Install dependencies
run: dotnet restore
- name: Prep project file
run: sed -i '/Scanner/d' ErsatzTV/ErsatzTV.csproj
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --no-restore --verbosity normal
build_and_test_mac:
runs-on: macos-latest
steps:
- name: Get the sources
uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
- name: Install dependencies
run: dotnet restore
- name: Prep project file
run: sed -i '' '/Scanner/d' ErsatzTV/ErsatzTV.csproj
- name: Build
run: dotnet build --configuration Release --no-restore

View File

@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Get the sources
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Extract Docker Tag
@@ -16,7 +16,7 @@ jobs:
run: |
tag=$(git describe --tags --abbrev=0)
echo "GIT_TAG=${tag:1}" >> $GITHUB_ENV
echo "DOCKER_TAG=${tag/-alpha/}" >> $GITHUB_ENV
echo "DOCKER_TAG=${tag/-beta/}" >> $GITHUB_ENV
- name: Extract Artifacts Version
shell: bash
run: |

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

1
.gitignore vendored
View File

@@ -43,3 +43,4 @@ scripts/generate-api-sdk/swagger.json
docker-compose.override.yml
ErsatzTV/wwwroot/v2/

View File

@@ -1,10 +1,456 @@
# Changelog
Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
## [0.7.2-beta] - 2023-01-05
### Fixed
- Fix VAAPI encoding in docker by switching to non-free driver
### Changed
- Rewrite log page to read directly from log files instead of sqlite
## [0.7.1-beta] - 2023-01-03
### Added
- Add new music video credit templates
### Fixed
- Fix many transcoding failures caused by the colorspace filter
- Fix song playback with VAAPI and NVENC
- Fix edge case where some local movies would not automatically be restored from trash
- Fix synchronizing Jellyfin and Emby collection items
- Fix saving some external subtitle records to database
### Changed
- Upgrade to dotnet 7
- Upgrade all docker images to ubuntu jammy and ffmpeg 5.1.2
- Limit library scan interval between 0 and 1,000,000
- 0 means do not automatically scan libraries
- 1 to 999,999 means scan if it has been that many hours since the last scan
- Use new `ErsatzTV.Scanner` process for scanning all libraries
- This should reduce the ongoing memory footprint
## [0.7.0-beta] - 2022-12-11
### Fixed
- Fix removing Jellyfin and Emby libraries that have been deleted from the source media server
- Fix `Work-Ahead HLS Segmenter Limit` setting to properly limit number of channels that can work-ahead at once
- Include base path value in generated channel playlist (M3U) and channel guide (XMLTV) links
- Fix parsing song metadata from OGG audio files
- Properly unlock/re-enable trakt list operations after an operation is canceled
### Added
- Add (required) bit depth normalization option to ffmpeg profile
- This can help if your card only supports e.g. h264 encoding, normalizing to 8 bits will allow the hardware encoder to be used
- Extract font attachments after extracting text subtitles
- This should improve SubStation Alpha subtitle rendering
- Detect VAAPI capabilities and fallback to software decoding/encoding as needed
- Add audio stream selector scripts for episodes and movies
- This will let you customize which audio stream is selected for playback
- Episodes are passed the following data:
- `channelNumber`
- `channelName`
- `showTitle`
- `showGuids`: array of string ids like `imdb_1234` or `tvdb_1234`
- `seasonNumber`
- `episodeNumber`
- `episodeGuids`: array of string ids like `imdb_1234` or `tvdb_1234`
- `preferredLanguageCodes`: array of string preferred language codes configured for the channel
- `audioStreams`: array of audio stream data, each containing
- `index`: the stream's index number, this is what the function needs to return
- `channels`: the number of audio channels
- `codec`: the audio codec
- `isDefault`: bool indicating whether the stream is flagged as default
- `isForced`: bool indicating whether the stream is flagged as forced
- `language`: the stream's language
- `title`: the stream's title
- Movies are passed the following data:
- `channelNumber`
- `channelName`
- `title`
- `guids`: array of string ids like `imdb_1234` or `tvdb_1234`
- `preferredLanguageCodes`: array of string preferred language codes configured for the channel
- `audioStreams`: array of audio stream data, each containing
- `index`: the stream's index number, this is what the function needs to return
- `channels`: the number of audio channels
- `codec`: the audio codec
- `isDefault`: bool indicating whether the stream is flagged as default
- `isForced`: bool indicating whether the stream is flagged as forced
- `language`: the stream's language
- `title`: the stream's title
- Add new fields to search index
- `video_codec`: the video codec
- `video_bit_depth`: the number of bits in the video stream's pixel format, e.g. 8 or 10
- `video_dynamic_range`: the video's dynamic range, either `sdr` or `hdr`
### Changed
- Change `Multi-Episode Shuffle` scripting system to use Javascript instead of Lua
## [0.6.9-beta] - 2022-10-21
### Fixed
- Fix bug where tail or fallback filler would sometimes schedule much longer than expected
- This only happened with fixed start schedule items following a schedule item with tail or fallback filler
- Fix NFO reader bug that caused inaccurate warning messages about invalid XML and incomplete metadata
- Fix reverse proxy SSL termination support by supporting `X-Forwarded-Proto` header
- Fix automatic playout reset scheduling
- Playouts would reset every 30 minutes between midnight and the configured time, instead of only at the configured time
- XMLTV: properly group schedule items with `Custom Title` followed by item(s) with `Guide Mode` set to `Filler`
### Added
- Add music video credits template system
- Templates are selected in each channel's settings
- Templates should be copied from `_default.ass.sbntxt` which is located in the config subfolder `templates/music-video-credits`
- Copy the file, give it any name ending with `.ass.sbntext`, and only make edits to the copied file
- The default template will be extracted and overwritten every time ErsatzTV is started
- The template is an [Advanced SubStation Alpha](http://www.tcax.org/docs/ass-specs.htm) file using [scribian](https://github.com/scriban/scriban/tree/master/doc) template syntax
- The following fields are available for use in the template:
- `resolution`: the ffmpeg profile's resolution, which is used for margin calculations
- `title`: the title of the music video
- `track`: the music video's track number
- `album`: the music video's album
- `plot`: the music video's plot
- `release_date`: the music video's release date
- `artist`: the music videos artist (the parent folder)
- `all_artists`: a list of additional artists from the music video's sidecar NFO metadata file
- `duration`: the timespan duration of the music video, which can be used to calculate timing of additional subtitles
- `stream_seek`: the timespan that ffmpeg will seek into the media item before beginning playback
- Add `Multi-Episode Shuffle` playout order for `Television Show` schedule items
- The purpose of this playout order is to improve randomization for shows that normally have intro, multiple episodes, and outro
- This playout order requires splitting the parts into individual files (e.g. splitting `s01e01-03.mkv` into `s01e01.mkv`, `s01e02.mkv` and `s01e03.mkv`)
- This playout order requires a lua script in the config subfolder `scripts/multi-episode-shuffle`
- The lua script should be named for the television show's guid, e.g. `tvdb_12345.lua` or `imdb_tt123456789.lua`
- The script defines the number of parts that each un-split file typically contains
- The script also defines a function to map each episode to a part number (or no part number i.e. `nil` if an episode has not been split)
- All groups of part numbers (i.e. all part 1s, all part 2s) will be shuffled
- The playout order will then schedule a random part 1 followed by a random part 2, etc
- Un-split (`nil`) episodes will be randomly placed between re-combined parts (e.g. part1, part2, part3, un-split, part1, part2, part3)
- Add `ETV_BASE_URL` environment variable to support reverse proxies that use paths (e.g. `/ersatztv`)
### Changed
- No longer place watermarks within content by default (e.g. within 4:3 content padded to a 16:9 resolution)
- This can be re-enabled if desired using the `Place Within Source Content` checkbox in watermark settings
## [0.6.8-beta] - 2022-10-05
### Fixed
- Fix typo introduced in `0.6.7-beta` that stopped QSV HEVC encoder from working
- Fix scaling logic for `Nvidia` acceleration and software mode
- Attempt to position watermarks within content (not over added black padding)
- Fix search results for `Other Videos` when NFO metadata is used
- Properly synchronize tags from Emby movies and shows
- Properly sync updated file paths from Plex
- Fix numeric range search queries (e.g. `minutes:[5 TO 10]`, `minutes:[* TO 3]`)
### Added
- Add `QSV Device` option to ffmpeg profile on linux
- Add guids to search index (e.g. `imdb:tt000000`, `tvdb:12345`)
## [0.6.7-beta] - 2022-09-05
### Fixed
- When all audio streams are selected with `HLS Direct`, explicitly copy them without transcoding
- This only happens when the channel does not have a `Preferred Audio Language`
- Fix scanner crash caused by invalid mtime
- `VAAPI`: Downgrade libva from 2.15 to 2.14
- Fix bug with XMLTV that caused some filler to display with primary content details
- Multiple fixes for content scaling with `Nvidia`, `Qsv` and `Vaapi` accelerations
- Properly scale image-based subtitles
- Fix bug where a schedule containing a single item (fixed start and flood) would never finish building a playout
- Logic was also added to detect infinite playout build loops in the future and stop them
- Fix bug where `Other Videos` wouldn't be included in scheduling mode `Shuffle In Order`
### Added
- Add `Preferred Audio Title` feature
- Preference can be configured in channel settings and overridden on schedule items
- When a title is specified, audio streams that contain that title (case-insensitive search) will be prioritized
- This can be helpful for creating channels that use commentary tracks
- External tooling exists to easily update title/name metadata if your audio streams don't already have this metadata
- Add `Amf` hardware acceleration option for AMD GPUs on Windows
- Add `QSV Extra Hardware Frames` parameter for tuning QSV acceleration
- Performance may improve on some systems after doubling or halving the default value of `64`
## [0.6.6-beta] - 2022-08-17
### Fixed
- Use MIME Type `application/x-mpegurl` for all playlists instead of `application/vnd.apple.mpegurl`
- Replace `setsar` filter with `setdar` filter
- `setsar` caused issues scaling between two different aspect ratios
- For example, some 4:3 content would appear stretched when scaled to a 16:9 resolution
- `setdar` is now only used when aspect ratios match
- Prioritize aspect ratio from container when video stream contains conflicting aspect ratio
- This is usually caused by bad authoring, but the change should improve scaling behavior for edge cases
### Added
- Support DSD audio file formats (DFF and DSF) in local song libraries
- Support OGG audio file formats (OGG, OPUS, OGA, OGX, SPX) in local song libraries
### Changed
- Always return playlist after a maximum of 8 seconds while starting up an HLS Segmenter session
- Use multi-variant playlists instead of redirects for HLS Segmenter sessions
- Upgrade ffmpeg from 5.0 to 5.1 in most docker images (not ARM variants)
- Upgrading from 5.0 to 5.1 is also recommended for other installations (Windows, Linux)
## [0.6.5-beta] - 2022-08-02
### Fixed
- Fix database initializer; fresh installs with v0.6.4-beta are missing some config data and should upgrade
## [0.6.4-beta] - 2022-07-28
### Fixed
- Fix subtitle stream selection when subtitle language is different than audio language
- Fix bug with unsupported AAC channel layouts
- Fix NVIDIA second-gen maxwell capabilities detection
- Return distinct search results for episodes and other videos that have the same title
- For example, two other videos both named `Trailer` would previously have displayed as one item in search results
- Fix schedules that would begin to repeat the same content in the same order after a couple of days
### Added
- Add `640x480` resolution
## [0.6.3-beta] - 2022-07-04
### Fixed
- Maintain stream continuity when playout is rebuilt for a channel that is actively being streamed
- Properly apply changes to episode title, sort title, outline and plot from Plex
- Fix search index for other videos and songs
- In previous versions, some libraries would incorrectly display only one item
- Properly display old versions of renamed items in trash
### Added
- Add `Minimum Log Level` option to `Settings` page
- Other methods of configuring the log level will no longer work
## [0.6.2-beta] - 2022-06-18
### Fixed
- Fix content repeating for up to a minute near the top of every hour
- Check whether hardware-accelerated hevc codecs are supported by the NVIDIA card
- Software codecs will be used if they are unsupported by the NVIDIA card
- Fix sorting of channel contents in EPG
- Fix Jellyfin admin user id sync
- Ignore disabled admins and admins who do not have access to all libraries
### Added
- Add 32-bit `arm` docker tags (`develop-arm` and `latest-arm`)
### Changed
- Regularly delete old segments from transcode folder while content is actively transcoding
- This should help reduce required disk space
- To further minimize required disk space, set `Work-Ahead HLS Segmenter Limit` to `0` in `Settings`
## [0.6.1-beta] - 2022-06-03
### Fixed
- Fix Jellyfin show library paging
- Properly locate and identify multiple Plex servers
- Properly restore `Unavailable`/`File Not Found` items when they are located on disk
### Added
- Add basic music video credits subtitle generation
- This can be enabled in channel settings
## [0.6.0-beta] - 2022-06-01
### Fixed
- Additional fix for duplicate `Other Videos` entries; trash may need to be emptied one last time after upgrading
- Fix watermark opacity in cultures where `,` is a decimal separator
- Rework playlist filtering to avoid empty playlist responses
- Fix some QSV/VAAPI memory errors by always requesting 64 extra hardware frames
### Added
- Enable QSV hardware acceleration for vaapi docker images
### Changed
- Use paging to synchronize all media from Plex, Jellyfin and Emby
- This will reduce memory use and improve reliability of synchronizing large libraries
- Disable low power mode for `h264_qsv` and `hevc_qsv` encoders
## [0.5.8-beta] - 2022-05-20
### Fixed
- Fix error display with `HLS Segmenter` and `MPEG-TS` streaming modes
- Remove erroneous log messages about normalizing framerate on channels where framerate normalization is disabled
- Fix unscheduled filler gaps that sometimes happen as playouts are automatically extended each hour
### Added
- Clean transcode cache folder on startup and after `HLS Segmenter` session terminates for any reason
### Changed
- Remove thread limitation for scenarios where it is not required
- This should give a performance boost to installations that don't use hardware acceleration
- Use hardware acceleration to display error messages where configured
## [0.5.7-beta] - 2022-05-14
### Fixed
- Reduce memory use due to library scan operations
- Fix some instances of filler getting "stuck" when a filler item is encountered that's too long for the gap
- Properly ignore Plex `Other Videos` libraries (`movie` libraries where agent is `com.plexapp.agents.none`)
- Fix `Custom Title` for schedule items with `One`, `Multiple` and `Flood` playout modes
- Fix scheduling bug where flood items would sometimes fail to continue after midnight
### Added
- Add `metadata_kind` field to search index to allow searching for items with a particular metdata source
- Valid metadata kinds are `fallback`, `sidecar` (NFO), `external` (from a media server) and `embedded` (songs)
- Add autocomplete functionality to search bar to quickly navigate to channels, ffmpeg profiles, collections and schedules by name
- Add global setting to skip missing (file-not-found or unavailable) items when building playouts
- Add filler preset option to allow watermarks to overlay on top of filler (disabled by default)
- This option is applied when new items are added to a playout; rebuilding is needed if you want the change to take effect immediately
- Read `track` field from music video NFO metadata and use it for chronological sorting (after release date)
- Add `Random Start Point` option to schedules
- When this option is enabled, all `Chronological` or `Shuffle In Order` content groups will have their start points randomized
- When this option is disabled, all `Chronological` or `Shuffle In Order` content groups will start with the chronologically earliest item
### Changed
- Replace invalid (control) characters in NFO metadata with replacement character `<60>` before parsing
- Store partial (incomplete) NFO metadata results when invalid XML is encountered
- Previously, no metadata would be stored if the XML within the NFO failed to validate
## [0.5.6-beta] - 2022-05-06
### Fixed
- Fix processing local movie NFO metadata without a `year` value
- Fix processing local movie fallback metadata
- Fix search edge case where very recently added items (hours) would not be returned by relative date queries
- Fix search index validation on startup; improper validation was causing a rebuild with every startup
- Block library scanning until search index has been recreated/upgraded
- Fix occasional erroneous log messages when HLS channel playback times out because all clients have left
- Fix fallback filler playback
- Fix stream continuity when error messages are displayed
- Fix duplicate scanning within `Other Video` libraries (i.e. folders would be scanned multiple times)
### Added
- Add `show_genre` and `show_tag` to search index for seasons and episodes
- Use `aired` value to source release date from music video nfo metadata
- Add NFO metadata support to `Other Video` libraries
- `Other Video` NFO metadata must be in the movie NFO metadata format
## [0.5.5-beta] - 2022-05-03
### Fixed
- Fix adding episodes with no title to the search index
- This behavior was preventing some items from being removed from the trash
- Support combination NFO metadata for movies, shows, artists and music videos
- Note that ErsatzTV does not scrape any metadata; any URLs after the XML will be ignored
- Fix bug causing some Jellyfin and Emby content to incorrectly show as unavailable
- Fix extracting embedded `mov_text` subtitles
- Properly extract embedded subtitles on playouts where subtitles are only enabled on schedule items (and not on the channel itself)
### Added
- Add experimental `arm64` docker tags (`develop-arm64` and `latest-arm64`)
- Use `Sort Title` from Movie NFO metadata if available
- Support multiple `Artist` entries in music video NFO metadata
## [0.5.4-beta] - 2022-04-29
### Fixed
- Cleanly stop all library scans when service termination is requested
- Fix health check crash when trash contains a show or a season
- Fix ability of health check crash to crash home page
- Remove and ignore Season 0/Specials from Plex shows that have no specials
- Automatically delete and rebuild the search index on startup if it has become corrupt
- Automatically scan Jellyfin and Emby libraries on startup and periodically
- Properly remove un-synchronized Plex, Jellyfin and Emby items from the database and search index
- Fix synchronizing movies within a collection from Jellyfin
### Changed
- Update Plex, Jellyfin and Emby movie and show library scanners to share a significant amount of code
- This should help maintain feature parity going forward
- Optimize search-index rebuilding to complete 100x faster
- **No longer use network paths to source content from Jellyfin and Emby**
- **If you previously used path replacements to convert network paths to local paths, you should remove them**
### Added
- Add `unavailable` state for Jellyfin and Emby movie and show libraries
- Add `height` and `width` to search index for all videos
- Add `season_number` and `episode_number` to search index for all episodes
- Add `season_number` to search index for seasons
- Add `show_title` to search index for seasons and episodes
## [0.5.3-beta] - 2022-04-24
### Fixed
- Cleanly stop Plex library scan when service termination is requested
- Fix bug introduced with 0.5.2-beta that prevented some Plex content from being played
- Fix spammy subtitle error message
- Fix generating blur hashes for song backgrounds in Docker
### Changed
- No longer remove Plex movies and episodes from ErsatzTV when they do not exist on disk
- Instead, a new `unavailable` media state will be used to indicate this condition
- After updating mounts, path replacements, etc - a library scan can be used to resolve this state
## [0.5.2-beta] - 2022-04-22
### Fixed
- Fix unlocking libraries when scanning fails for any reason
- Fix software overlay of actual size watermark
### Added
- Add support for burning in embedded and external text subtitles
- **This requires a one-time full library scan, which may take a long time with large libraries.**
- Sync Plex, Jellyfin and Emby collections as tags on movies, shows, seasons and episodes
- This allows smart collections that use queries like `tag:"Collection Name"`
- Note that Emby has an outstanding collections bug that prevents updates when removing items from a collection
- Sync Plex labels as tags on movies and shows
- This allows smart collections that use queries like `tag:"Plex Label Name"`
- Add `Deep Scan` button for Plex libraries
- This scanning mode is *slow* but is required to detect some changes like labels
### Changed
- Improve the speed and change detection of the Plex library scanners
## [0.5.1-beta] - 2022-04-17
### Fixed
- Fix subtitles edge case with NVENC
- Only select picture subtitles (text subtitles are not yet supported)
- Supported picture subtitles are `hdmv_pgs_subtitle` and `dvd_subtitle`
- Fix subtitles using software encoders, videotoolbox, VAAPI
- Fix setting VAAPI driver name
- Fix ffmpeg troubleshooting reports
- Fix bug where filler would behave as if it were configured to pad even though a different mode was selected
- Fix bug where mid-roll count filler would skip scheduling the final chapter in an episode
### Added
- Add `Empty Trash` button to `Trash` page
## [0.5.0-beta] - 2022-04-13
### Fixed
- Fix `HLS Segmenter` bug where it would drift off of the schedule if a playout was changed while the segmenter was running
- Ensure clients that use HDHomeRun emulation (like Plex) always get an `MPEG-TS` stream, regardless of the configured streaming mode
- Fix scheduling bug that caused some days to be skipped when fixed start times were used with fallback filler
### Added
- Add `Preferred Subtitle Language` and `Subtitle Mode` to channel settings
- `Preferred Subtitle Language` will filter all subtitle streams based on language
- `Subtitle Mode` will further filter subtitle streams based on attributes (forced, default)
- If picture-based subtitles are found after filtering, they will be burned into the video stream
- Detect non-zero ffmpeg exit code from `HLS Segmenter` and `MPEG-TS`, log error output and display error output on stream
- Add `Watermark` setting to schedule items; this allows override the channel watermark. Watermark priority is:
- Schedule Item
- Channel
- Global
### Changed
- Remove legacy transcoder logic option; all channels will use the new transcoder logic
- Renamed channel setting `Preferred Language` to `Preferred Audio Language`
- Reworked playout build logic to maintain collection progress in some scenarios. There are now three build modes:
- `Continue` - add new items to the end of an existing playout
- This mode is used when playouts are automatically extended in the background
- `Refresh` - this mode will try to maintain collection progress while rebuilding the entire playout
- This mode is used when a schedule is updated, or when collection modifications trigger a playout rebuild
- `Reset` - this mode will rebuild the entire playout and will NOT maintain progress
- This mode is only used when the `Reset Playout` button is clicked on the Playouts page
- **This requires rebuilding all playouts, which will happen on startup after upgrading**
- Use ffmpeg to resize images; this should help reduce ErsatzTV's memory use
- Use ffprobe to check for animated logos and watermarks; this should help reduce ErsatzTV's memory use
- Allow two decimals in channel numbers (e.g. `5.73`)
## [0.4.5-alpha] - 2022-03-29
### Fixed
- Fix streaming mode inconsistencies when `mode` parameter is unspecified
- Fix startup on Windows 7
### Added
- Add option to automatically deinterlace video when transcoding
- Previously, this was always enabled; the purpose of the option is to allow disabling any deinterlace filters
- Note that there is no performance gain to disabling the option with progressive content; filters are only ever applied to interlaced content
### Changed
- Change FFmpeg Profile video codec and audio codec text fields to select fields
- The appropriate video encoder will be determined based on the video format and hardware acceleration selections
- Remove FFmpeg Profile `Transcode`, `Normalize Video` and `Normalize Audio` settings
- All content will be transcoded and have audio and video normalized
- The only exception to this rule is `HLS Direct` streaming mode, which directly copies video and audio streams
- Always try to connect to Plex at `http://localhost:32400` even if that address isn't advertised by the Plex API
- If Plex isn't on the localhost, all other addresses will be checked as with previous releases
## [0.4.4-alpha] - 2022-03-10
### Fixed
- Fix `HLS Direct` streaming mode
@@ -1017,7 +1463,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Initial release to facilitate testing outside of Docker.
[Unreleased]: https://github.com/jasongdove/ErsatzTV/compare/v0.4.4-alpha...HEAD
[Unreleased]: https://github.com/jasongdove/ErsatzTV/compare/v0.7.2-beta...HEAD
[0.7.2-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.7.1-beta...v0.7.2-beta
[0.7.1-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.7.0-beta...v0.7.1-beta
[0.7.0-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.9-beta...v0.7.0-beta
[0.6.9-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.8-beta...v0.6.9-beta
[0.6.8-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.7-beta...v0.6.8-beta
[0.6.7-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.6-beta...v0.6.7-beta
[0.6.6-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.5-beta...v0.6.6-beta
[0.6.5-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.4-beta...v0.6.5-beta
[0.6.4-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.3-beta...v0.6.4-beta
[0.6.3-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.2-beta...v0.6.3-beta
[0.6.2-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.1-beta...v0.6.2-beta
[0.6.1-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.6.0-beta...v0.6.1-beta
[0.6.0-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.5.8-beta...v0.6.0-beta
[0.5.8-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.5.7-beta...v0.5.8-beta
[0.5.7-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.5.6-beta...v0.5.7-beta
[0.5.6-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.5.5-beta...v0.5.6-beta
[0.5.5-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.5.4-beta...v0.5.5-beta
[0.5.4-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.5.3-beta...v0.5.4-beta
[0.5.3-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.5.2-beta...v0.5.3-beta
[0.5.2-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.5.1-beta...v0.5.2-beta
[0.5.1-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.5.0-beta...v0.5.1-beta
[0.5.0-beta]: https://github.com/jasongdove/ErsatzTV/compare/v0.4.5-alpha...v0.5.0-beta
[0.4.5-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.4.4-alpha...v0.4.5-alpha
[0.4.4-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.4.3-alpha...v0.4.4-alpha
[0.4.3-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.4.2-alpha...v0.4.3-alpha
[0.4.2-alpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.4.1-alpha...v0.4.2-alpha
@@ -1103,4 +1572,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
[0.0.5-prealpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.0.4-prealpha...v0.0.5-prealpha
[0.0.4-prealpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.0.3-prealpha...v0.0.4-prealpha
[0.0.3-prealpha]: https://github.com/jasongdove/ErsatzTV/compare/v0.0.1-prealpha...v0.0.3-prealpha
[0.0.1-prealpha]: https://github.com/jasongdove/ErsatzTV/releases/tag/v0.0.1-prealpha
[0.0.1-prealpha]: https://github.com/jasongdove/ErsatzTV/releases/tag/v0.0.1-prealpha

2
ErsatzTV-Windows/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
target/

1028
ErsatzTV-Windows/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
[package]
name = "ersatztv_windows"
version = "0.1.0"
edition = "2021"
[dependencies]
tray-item = { git = "https://github.com/olback/tray-item-rs" }
special-folder = { git = "https://github.com/masinc/special-folder-rs" }
process_path = "0.1.4"
[dependencies.windows]
version = "0.43.0"
features = [
"Win32_System_Console",
"Win32_Foundation"
]
[build-dependencies]
windres = "*"

View File

@@ -1,33 +0,0 @@
<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="Asmichi.ChildProcess" Version="0.11.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ErsatzTV.Core\ErsatzTV.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Program.cs">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Compile>
</ItemGroup>
</Project>

View File

@@ -1,14 +0,0 @@
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

@@ -1,77 +0,0 @@
using ErsatzTV.Core;
using System.Diagnostics;
using Asmichi.ProcessManagement;
using System.Reflection;
namespace ErsatzTV_Windows;
public class TrayApplicationContext : ApplicationContext
{
private readonly NotifyIcon _trayIcon;
private readonly IChildProcess? _childProcess;
public TrayApplicationContext()
{
_trayIcon = new NotifyIcon
{
Icon = new Icon("./Ersatztv.ico"),
ContextMenuStrip = new ContextMenuStrip(),
Visible = true
};
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))
{
var si = new ChildProcessStartInfo(exe);
_childProcess = ChildProcess.Start(si);
}
}
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)
{
_childProcess?.Dispose();
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();
}
}

View File

@@ -0,0 +1,5 @@
use windres::Build;
fn main() {
Build::new().compile("ersatztv_windows.rc").unwrap();
}

View File

@@ -0,0 +1,2 @@
id ICON "ersatztv.ico"
ersatztv-icon ICON "ersatztv.ico"

View File

@@ -0,0 +1,112 @@
#![windows_subsystem = "windows"]
use special_folder::SpecialFolder;
use std::fs;
use std::os::windows::process::CommandExt;
use std::process::Child;
use std::process::Command;
use std::process::Stdio;
use windows::Win32::System::Console;
use {std::sync::mpsc, tray_item::TrayItem};
const CREATE_NO_WINDOW: u32 = 0x08000000;
enum Message {
Exit,
}
fn main() {
let mut tray = TrayItem::new("ErsatzTV", "ersatztv-icon").unwrap();
let (tx, rx) = mpsc::channel();
tray.add_menu_item("Launch Web UI", || {
let _ = Command::new("cmd")
.creation_flags(CREATE_NO_WINDOW)
.arg("/C")
.arg("start")
.arg("http://localhost:8409")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn();
})
.unwrap();
tray.add_menu_item("Show Logs", || {
let path = SpecialFolder::LocalApplicationData
.get()
.unwrap()
.join("ersatztv")
.join("logs");
match path.to_str() {
None => {}
Some(folder) => {
fs::create_dir_all(folder).unwrap();
let _ = Command::new("cmd")
.creation_flags(CREATE_NO_WINDOW)
.arg("/C")
.arg("start")
.arg(folder)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn();
}
}
})
.unwrap();
tray.inner_mut().add_separator().unwrap();
tray.add_menu_item("Exit", move || {
tx.send(Message::Exit).unwrap();
})
.unwrap();
let path = process_path::get_executable_path();
let mut child: Option<Child> = None;
match path {
None => {}
Some(path) => {
let etv = path.parent().unwrap().join("ErsatzTV.exe");
if etv.exists() {
match etv.to_str() {
None => {}
Some(etv) => {
child = Some(
Command::new(etv)
.creation_flags(CREATE_NO_WINDOW)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap(),
);
}
}
}
}
}
loop {
match rx.recv() {
Ok(Message::Exit) => {
match child {
None => {}
Some(mut child) => {
unsafe {
if Console::AttachConsole(child.id()) == true
{
Console::GenerateConsoleCtrlEvent(Console::CTRL_C_EVENT, 0);
}
}
child.wait().unwrap();
}
}
break;
}
_ => {}
}
}
}

View File

@@ -11,4 +11,4 @@ public record ArtistViewModel(
List<string> Genres,
List<string> Styles,
List<string> Moods,
List<CultureInfo> Languages);
List<CultureInfo> Languages);

View File

@@ -37,4 +37,4 @@ internal static class Mapper
.Flatten()
.ToList();
}
}
}

View File

@@ -2,4 +2,4 @@
namespace ErsatzTV.Application.Artists;
public record GetAllArtists : IRequest<List<NamedMediaItemViewModel>>;
public record GetAllArtists : IRequest<List<NamedMediaItemViewModel>>;

View File

@@ -19,4 +19,4 @@ public class GetAllArtistsHandler : IRequestHandler<GetAllArtists, List<NamedMed
a => !string.IsNullOrWhiteSpace(
a.ArtistMetadata.HeadOrNone().Match(am => am.Title, () => string.Empty))))
.Map(list => list.Map(ProjectToViewModel).ToList());
}
}

View File

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

View File

@@ -29,4 +29,4 @@ public class GetArtistByIdHandler : IRequestHandler<GetArtistById, Option<Artist
},
() => Task.FromResult(Option<ArtistViewModel>.None));
}
}
}

View File

@@ -10,8 +10,13 @@ public record ChannelViewModel(
string Categories,
int FFmpegProfileId,
string Logo,
string PreferredLanguageCode,
string PreferredAudioLanguageCode,
string PreferredAudioTitle,
StreamingMode StreamingMode,
int? WatermarkId,
int? FallbackFillerId,
int PlayoutCount);
int PlayoutCount,
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate);

View File

@@ -11,7 +11,12 @@ public record CreateChannel
string Categories,
int FFmpegProfileId,
string Logo,
string PreferredLanguageCode,
string PreferredAudioLanguageCode,
string PreferredAudioTitle,
StreamingMode StreamingMode,
int? WatermarkId,
int? FallbackFillerId) : IRequest<Either<BaseError, CreateChannelResult>>;
int? FallbackFillerId,
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate) : IRequest<Either<BaseError, CreateChannelResult>>;

View File

@@ -21,7 +21,7 @@ public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseEr
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Channel> validation = await Validate(dbContext, request);
return await LanguageExtensions.Apply(validation, c => PersistChannel(dbContext, c));
return await validation.Apply(c => PersistChannel(dbContext, c));
}
private static async Task<CreateChannelResult> PersistChannel(TvContext dbContext, Channel channel)
@@ -34,11 +34,19 @@ public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseEr
private static async Task<Validation<BaseError, Channel>> Validate(TvContext dbContext, CreateChannel request) =>
(ValidateName(request), await ValidateNumber(dbContext, request),
await FFmpegProfileMustExist(dbContext, request),
ValidatePreferredLanguage(request),
ValidatePreferredAudioLanguage(request),
ValidatePreferredSubtitleLanguage(request),
await WatermarkMustExist(dbContext, request),
await FillerPresetMustExist(dbContext, request))
.Apply(
(name, number, ffmpegProfileId, preferredLanguageCode, watermarkId, fillerPresetId) =>
(
name,
number,
ffmpegProfileId,
preferredAudioLanguageCode,
preferredSubtitleLanguageCode,
watermarkId,
fillerPresetId) =>
{
var artwork = new List<Artwork>();
if (!string.IsNullOrWhiteSpace(request.Logo))
@@ -62,7 +70,12 @@ public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseEr
FFmpegProfileId = ffmpegProfileId,
StreamingMode = request.StreamingMode,
Artwork = artwork,
PreferredLanguageCode = preferredLanguageCode
PreferredAudioLanguageCode = preferredAudioLanguageCode,
PreferredAudioTitle = request.PreferredAudioTitle,
PreferredSubtitleLanguageCode = preferredSubtitleLanguageCode,
SubtitleMode = request.SubtitleMode,
MusicVideoCreditsMode = request.MusicVideoCreditsMode,
MusicVideoCreditsTemplate = request.MusicVideoCreditsTemplate
};
foreach (int id in watermarkId)
@@ -82,14 +95,23 @@ public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseEr
createChannel.NotEmpty(c => c.Name)
.Bind(_ => createChannel.NotLongerThan(50)(c => c.Name));
private static Validation<BaseError, string> ValidatePreferredLanguage(CreateChannel createChannel) =>
Optional(createChannel.PreferredLanguageCode ?? string.Empty)
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 language code is invalid");
.ToValidation<BaseError>("Preferred audio language code is invalid");
private static async Task<Validation<BaseError, string>> ValidateNumber(TvContext dbContext, CreateChannel createChannel)
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);
@@ -102,7 +124,7 @@ public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseEr
return createChannel.Number;
}
return BaseError.New("Invalid channel number; one decimal is allowed for subchannels");
return BaseError.New("Invalid channel number; two decimals are allowed for subchannels");
});
}
@@ -152,4 +174,4 @@ public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseEr
o => o.ToValidation<BaseError>(
$"Fallback filler {createChannel.FallbackFillerId} does not exist."));
}
}
}

View File

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

View File

@@ -2,4 +2,4 @@
namespace ErsatzTV.Application.Channels;
public record DeleteChannel(int ChannelId) : IRequest<Either<BaseError, Task>>;
public record DeleteChannel(int ChannelId) : IRequest<Either<BaseError, Task>>;

View File

@@ -20,4 +20,4 @@ public class DeleteChannelHandler : IRequestHandler<DeleteChannel, Either<BaseEr
(await _channelRepository.Get(deleteChannel.ChannelId))
.ToValidation<BaseError>($"Channel {deleteChannel.ChannelId} does not exist.")
.Map(c => c.Id);
}
}

View File

@@ -12,7 +12,12 @@ public record UpdateChannel
string Categories,
int FFmpegProfileId,
string Logo,
string PreferredLanguageCode,
string PreferredAudioLanguageCode,
string PreferredAudioTitle,
StreamingMode StreamingMode,
int? WatermarkId,
int? FallbackFillerId) : IRequest<Either<BaseError, ChannelViewModel>>;
int? FallbackFillerId,
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode SubtitleMode,
ChannelMusicVideoCreditsMode MusicVideoCreditsMode,
string MusicVideoCreditsTemplate) : IRequest<Either<BaseError, ChannelViewModel>>;

View File

@@ -1,20 +1,29 @@
using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading.Channels;
using ErsatzTV.Application.Subtitles;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.Channels.Mapper;
using Channel = ErsatzTV.Core.Domain.Channel;
namespace ErsatzTV.Application.Channels;
public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseError, ChannelViewModel>>
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly ChannelWriter<ISubtitleWorkerRequest> _ffmpegWorkerChannel;
public UpdateChannelHandler(IDbContextFactory<TvContext> dbContextFactory) =>
public UpdateChannelHandler(
ChannelWriter<ISubtitleWorkerRequest> ffmpegWorkerChannel,
IDbContextFactory<TvContext> dbContextFactory)
{
_ffmpegWorkerChannel = ffmpegWorkerChannel;
_dbContextFactory = dbContextFactory;
}
public async Task<Either<BaseError, ChannelViewModel>> Handle(
UpdateChannel request,
@@ -22,7 +31,7 @@ public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseEr
{
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));
return await validation.Apply(c => ApplyUpdateRequest(dbContext, c, request));
}
private async Task<ChannelViewModel> ApplyUpdateRequest(TvContext dbContext, Channel c, UpdateChannel update)
@@ -32,7 +41,12 @@ public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseEr
c.Group = update.Group;
c.Categories = update.Categories;
c.FFmpegProfileId = update.FFmpegProfileId;
c.PreferredLanguageCode = update.PreferredLanguageCode;
c.PreferredAudioLanguageCode = update.PreferredAudioLanguageCode;
c.PreferredAudioTitle = update.PreferredAudioTitle;
c.PreferredSubtitleLanguageCode = update.PreferredSubtitleLanguageCode;
c.SubtitleMode = update.SubtitleMode;
c.MusicVideoCreditsMode = update.MusicVideoCreditsMode;
c.MusicVideoCreditsTemplate = update.MusicVideoCreditsTemplate;
c.Artwork ??= new List<Artwork>();
if (!string.IsNullOrWhiteSpace(update.Logo))
@@ -58,18 +72,30 @@ public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseEr
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),
ValidatePreferredLanguage(request))
ValidatePreferredAudioLanguage(request))
.Apply((channelToUpdate, _, _, _) => channelToUpdate);
private static Task<Validation<BaseError, Channel>> ChannelMustExist(
@@ -100,16 +126,16 @@ public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseEr
return updateChannel.Number;
}
return BaseError.New("Invalid channel number; one decimal is allowed for subchannels");
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> ValidatePreferredLanguage(UpdateChannel updateChannel) =>
Optional(updateChannel.PreferredLanguageCode ?? string.Empty)
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 language code is invalid");
}
.ToValidation<BaseError>("Preferred audio language code is invalid");
}

View File

@@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Api.Channels;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.Channels;
@@ -13,17 +14,37 @@ internal static class Mapper
channel.Categories,
channel.FFmpegProfileId,
GetLogo(channel),
channel.PreferredLanguageCode,
channel.PreferredAudioLanguageCode,
channel.PreferredAudioTitle,
channel.StreamingMode,
channel.WatermarkId,
channel.FallbackFillerId,
channel.Playouts?.Count ?? 0);
channel.Playouts?.Count ?? 0,
channel.PreferredSubtitleLanguageCode,
channel.SubtitleMode,
channel.MusicVideoCreditsMode,
channel.MusicVideoCreditsTemplate);
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 GetWatermark(Channel channel) =>
Optional(channel.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Watermark))
.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,3 +1,3 @@
namespace ErsatzTV.Application.Channels;
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

@@ -11,4 +11,4 @@ public class GetAllChannelsHandler : IRequestHandler<GetAllChannels, List<Channe
public async Task<List<ChannelViewModel>> Handle(GetAllChannels request, CancellationToken cancellationToken) =>
Optional(await _channelRepository.GetAll()).Flatten().Map(ProjectToViewModel).ToList();
}
}

View File

@@ -1,3 +1,3 @@
namespace ErsatzTV.Application.Channels;
public record GetChannelById(int Id) : IRequest<Option<ChannelViewModel>>;
public record GetChannelById(int Id) : IRequest<Option<ChannelViewModel>>;

View File

@@ -12,4 +12,4 @@ public class GetChannelByIdHandler : IRequestHandler<GetChannelById, Option<Chan
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

@@ -21,11 +21,22 @@ public class GetChannelFramerateHandler : IRequestHandler<GetChannelFramerate, O
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);
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
List<Playout> playouts = await dbContext.Playouts
.Include(p => p.Items)
.ThenInclude(pi => pi.MediaItem)

View File

@@ -2,4 +2,4 @@
namespace ErsatzTV.Application.Channels;
public record GetChannelGuide(string Scheme, string Host) : IRequest<ChannelGuide>;
public record GetChannelGuide(string Scheme, string Host, string BaseUrl) : IRequest<ChannelGuide>;

View File

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

View File

@@ -2,4 +2,4 @@
namespace ErsatzTV.Application.Channels;
public record GetChannelLineup(string Scheme, string Host) : IRequest<List<LineupItem>>;
public record GetChannelLineup(string Scheme, string Host) : IRequest<List<LineupItem>>;

View File

@@ -12,4 +12,4 @@ public class GetChannelLineupHandler : IRequestHandler<GetChannelLineup, List<Li
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

@@ -2,4 +2,4 @@
namespace ErsatzTV.Application.Channels;
public record GetChannelPlaylist(string Scheme, string Host, string Mode) : IRequest<ChannelPlaylist>;
public record GetChannelPlaylist(string Scheme, string Host, string BaseUrl, string Mode) : IRequest<ChannelPlaylist>;

View File

@@ -14,7 +14,7 @@ public class GetChannelPlaylistHandler : IRequestHandler<GetChannelPlaylist, Cha
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));
.Map(channels => new ChannelPlaylist(request.Scheme, request.Host, request.BaseUrl, channels));
private static List<Channel> EnsureMode(IEnumerable<Channel> channels, string mode)
{
@@ -47,4 +47,4 @@ public class GetChannelPlaylistHandler : IRequestHandler<GetChannelPlaylist, Cha
return result;
}
}
}

View File

@@ -2,4 +2,4 @@
namespace ErsatzTV.Application.Configuration;
public record SaveConfigElementByKey(ConfigElementKey Key, string Value) : MediatR.IRequest<Unit>;
public record SaveConfigElementByKey(ConfigElementKey Key, string Value) : IRequest<Unit>;

View File

@@ -2,7 +2,7 @@
namespace ErsatzTV.Application.Configuration;
public class SaveConfigElementByKeyHandler : MediatR.IRequestHandler<SaveConfigElementByKey, Unit>
public class SaveConfigElementByKeyHandler : IRequestHandler<SaveConfigElementByKey, Unit>
{
private readonly IConfigElementRepository _configElementRepository;
@@ -14,4 +14,4 @@ public class SaveConfigElementByKeyHandler : MediatR.IRequestHandler<SaveConfigE
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

@@ -2,4 +2,4 @@
namespace ErsatzTV.Application.Configuration;
public record UpdateLibraryRefreshInterval(int LibraryRefreshInterval) : MediatR.IRequest<Either<BaseError, Unit>>;
public record UpdateLibraryRefreshInterval(int LibraryRefreshInterval) : IRequest<Either<BaseError, Unit>>;

View File

@@ -5,7 +5,7 @@ using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Configuration;
public class UpdateLibraryRefreshIntervalHandler :
MediatR.IRequestHandler<UpdateLibraryRefreshInterval, Either<BaseError, Unit>>
IRequestHandler<UpdateLibraryRefreshInterval, Either<BaseError, Unit>>
{
private readonly IConfigElementRepository _configElementRepository;
@@ -16,13 +16,16 @@ public class UpdateLibraryRefreshIntervalHandler :
UpdateLibraryRefreshInterval request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(_ => _configElementRepository.Upsert(ConfigElementKey.LibraryRefreshInterval, request.LibraryRefreshInterval))
.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)
.Where(lri => lri is >= 0 and < 1_000_000)
.Map(_ => Unit.Default)
.ToValidation<BaseError>("Tuner count must be greater than zero")
.ToValidation<BaseError>("Library refresh interval must be zero or greated")
.AsTask();
}
}

View File

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

View File

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

View File

@@ -3,19 +3,19 @@ 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
UpdatePlayoutDaysToBuildHandler : MediatR.IRequestHandler<UpdatePlayoutDaysToBuild, Either<BaseError, Unit>>
public class UpdatePlayoutSettingsHandler : IRequestHandler<UpdatePlayoutSettings, Either<BaseError, Unit>>
{
private readonly IConfigElementRepository _configElementRepository;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
public UpdatePlayoutDaysToBuildHandler(
public UpdatePlayoutSettingsHandler(
IConfigElementRepository configElementRepository,
IDbContextFactory<TvContext> dbContextFactory,
ChannelWriter<IBackgroundServiceRequest> workerChannel)
@@ -26,34 +26,37 @@ public class
}
public async Task<Either<BaseError, Unit>> Handle(
UpdatePlayoutDaysToBuild request,
UpdatePlayoutSettings request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Unit> validation = await Validate(request);
return await validation.Apply<Unit, Unit>(_ => ApplyUpdate(dbContext, request.DaysToBuild));
return await validation.Apply<Unit, Unit>(_ => ApplyUpdate(dbContext, request.PlayoutSettings));
}
private async Task<Unit> ApplyUpdate(TvContext dbContext, int daysToBuild)
private async Task<Unit> ApplyUpdate(TvContext dbContext, PlayoutSettingsViewModel playoutSettings)
{
await _configElementRepository.Upsert(ConfigElementKey.PlayoutDaysToBuild, daysToBuild);
// build all playouts to proper number of days
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));
await _workerChannel.WriteAsync(new BuildPlayout(playoutId, PlayoutBuildMode.Continue));
}
return Unit.Default;
}
private static Task<Validation<BaseError, Unit>> Validate(UpdatePlayoutDaysToBuild request) =>
Optional(request.DaysToBuild)
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

@@ -1,3 +1,3 @@
namespace ErsatzTV.Application.Configuration;
public record ConfigElementViewModel(string Key, string Value);
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

@@ -6,4 +6,4 @@ 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

@@ -2,4 +2,4 @@
namespace ErsatzTV.Application.Configuration;
public record GetConfigElementByKey(ConfigElementKey Key) : IRequest<Option<ConfigElementViewModel>>;
public record GetConfigElementByKey(ConfigElementKey Key) : IRequest<Option<ConfigElementViewModel>>;

View File

@@ -14,4 +14,4 @@ public class GetConfigElementByKeyHandler : IRequestHandler<GetConfigElementByKe
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

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

View File

@@ -13,4 +13,4 @@ public class GetLibraryRefreshIntervalHandler : IRequestHandler<GetLibraryRefres
public Task<int> Handle(GetLibraryRefreshInterval request, CancellationToken cancellationToken) =>
_configElementRepository.GetValue<int>(ConfigElementKey.LibraryRefreshInterval)
.Map(result => result.IfNone(6));
}
}

View File

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

View File

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

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,55 @@
using System.Threading.Channels;
using ErsatzTV.Application.Libraries;
using ErsatzTV.Core;
using ErsatzTV.FFmpeg.Runtime;
namespace ErsatzTV.Application.Emby;
public class CallEmbyLibraryScannerHandler : CallLibraryScannerHandler,
IRequestHandler<ForceSynchronizeEmbyLibraryById, Either<BaseError, string>>,
IRequestHandler<SynchronizeEmbyLibraryByIdIfNeeded, Either<BaseError, string>>
{
public CallEmbyLibraryScannerHandler(
ChannelWriter<ISearchIndexBackgroundServiceRequest> channel,
IMediator mediator,
IRuntimeInfo runtimeInfo)
: base(channel, mediator, runtimeInfo)
{
}
Task<Either<BaseError, string>> IRequestHandler<ForceSynchronizeEmbyLibraryById, Either<BaseError, string>>.Handle(
ForceSynchronizeEmbyLibraryById request,
CancellationToken cancellationToken) => Handle(request, cancellationToken);
Task<Either<BaseError, string>> IRequestHandler<SynchronizeEmbyLibraryByIdIfNeeded, Either<BaseError, string>>.Handle(
SynchronizeEmbyLibraryByIdIfNeeded request,
CancellationToken cancellationToken) => Handle(request, cancellationToken);
private async Task<Either<BaseError, string>> Handle(
ISynchronizeEmbyLibraryById request,
CancellationToken cancellationToken)
{
Validation<BaseError, string> validation = Validate();
return await validation.Match(
scanner => PerformScan(scanner, request, cancellationToken),
error => Task.FromResult<Either<BaseError, string>>(error.Join()));
}
private async Task<Either<BaseError, string>> PerformScan(
string scanner,
ISynchronizeEmbyLibraryById request,
CancellationToken cancellationToken)
{
var arguments = new List<string>
{
"scan-emby", request.EmbyLibraryId.ToString()
};
if (request.ForceScan)
{
arguments.Add("--force");
}
return await base.PerformScan(scanner, arguments, cancellationToken);
}
}

View File

@@ -2,4 +2,4 @@
namespace ErsatzTV.Application.Emby;
public record DisconnectEmby : MediatR.IRequest<Either<BaseError, Unit>>;
public record DisconnectEmby : IRequest<Either<BaseError, Unit>>;

View File

@@ -7,7 +7,7 @@ using ErsatzTV.Core.Interfaces.Search;
namespace ErsatzTV.Application.Emby;
public class DisconnectEmbyHandler : MediatR.IRequestHandler<DisconnectEmby, Either<BaseError, Unit>>
public class DisconnectEmbyHandler : IRequestHandler<DisconnectEmby, Either<BaseError, Unit>>
{
private readonly IEmbySecretStore _embySecretStore;
private readonly IEntityLocker _entityLocker;
@@ -38,4 +38,4 @@ public class DisconnectEmbyHandler : MediatR.IRequestHandler<DisconnectEmby, Eit
return Unit.Default;
}
}
}

View File

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

View File

@@ -6,7 +6,7 @@ using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Emby;
public class SaveEmbySecretsHandler : MediatR.IRequestHandler<SaveEmbySecrets, Either<BaseError, Unit>>
public class SaveEmbySecretsHandler : IRequestHandler<SaveEmbySecrets, Either<BaseError, Unit>>
{
private readonly ChannelWriter<IEmbyBackgroundServiceRequest> _channel;
private readonly IEmbyApiClient _embyApiClient;
@@ -53,4 +53,4 @@ public class SaveEmbySecretsHandler : MediatR.IRequestHandler<SaveEmbySecrets, E
}
private record Parameters(EmbySecrets Secrets, EmbyServerInformation ServerInformation);
}
}

View File

@@ -2,5 +2,5 @@
namespace ErsatzTV.Application.Emby;
public record SynchronizeEmbyLibraries(int EmbyMediaSourceId) : MediatR.IRequest<Either<BaseError, Unit>>,
IEmbyBackgroundServiceRequest;
public record SynchronizeEmbyLibraries(int EmbyMediaSourceId) : IRequest<Either<BaseError, Unit>>,
IEmbyBackgroundServiceRequest;

View File

@@ -8,8 +8,7 @@ using Microsoft.Extensions.Logging;
namespace ErsatzTV.Application.Emby;
public class
SynchronizeEmbyLibrariesHandler : MediatR.IRequestHandler<SynchronizeEmbyLibraries, Either<BaseError, Unit>>
public class SynchronizeEmbyLibrariesHandler : IRequestHandler<SynchronizeEmbyLibraries, Either<BaseError, Unit>>
{
private readonly IEmbyApiClient _embyApiClient;
private readonly IEmbySecretStore _embySecretStore;
@@ -72,32 +71,33 @@ public class
connectionParameters.ActiveConnection.Address,
connectionParameters.ApiKey);
await maybeLibraries.Match(
async libraries =>
{
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();
List<int> ids = await _mediaSourceRepository.UpdateLibraries(
connectionParameters.EmbyMediaSource.Id,
toAdd,
toRemove);
if (ids.Any())
{
await _searchIndex.RemoveItems(ids);
_searchIndex.Commit();
}
},
error =>
{
_logger.LogWarning(
"Unable to synchronize libraries from emby server {EmbyServer}: {Error}",
connectionParameters.EmbyMediaSource.ServerName,
error.Value);
foreach (BaseError error in maybeLibraries.LeftToSeq())
{
_logger.LogWarning(
"Unable to synchronize libraries from emby server {EmbyServer}: {Error}",
connectionParameters.EmbyMediaSource.ServerName,
error.Value);
}
return Task.CompletedTask;
});
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;
}
@@ -108,4 +108,4 @@ public class
{
public string ApiKey { get; set; }
}
}
}

View File

@@ -17,4 +17,4 @@ public record SynchronizeEmbyLibraryByIdIfNeeded(int EmbyLibraryId) : ISynchroni
public record ForceSynchronizeEmbyLibraryById(int EmbyLibraryId) : ISynchronizeEmbyLibraryById
{
public bool ForceScan => true;
}
}

View File

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

View File

@@ -32,4 +32,4 @@ public class SynchronizeEmbyMediaSourcesHandler : IRequestHandler<SynchronizeEmb
return mediaSources;
}
}
}

View File

@@ -3,6 +3,6 @@
namespace ErsatzTV.Application.Emby;
public record UpdateEmbyLibraryPreferences
(List<EmbyLibraryPreference> Preferences) : MediatR.IRequest<Either<BaseError, Unit>>;
(List<EmbyLibraryPreference> Preferences) : IRequest<Either<BaseError, Unit>>;
public record EmbyLibraryPreference(int Id, bool ShouldSyncItems);
public record EmbyLibraryPreference(int Id, bool ShouldSyncItems);

View File

@@ -5,7 +5,7 @@ using ErsatzTV.Core.Interfaces.Search;
namespace ErsatzTV.Application.Emby;
public class
UpdateEmbyLibraryPreferencesHandler : MediatR.IRequestHandler<UpdateEmbyLibraryPreferences,
UpdateEmbyLibraryPreferencesHandler : IRequestHandler<UpdateEmbyLibraryPreferences,
Either<BaseError, Unit>>
{
private readonly IMediaSourceRepository _mediaSourceRepository;
@@ -33,4 +33,4 @@ public class
return Unit.Default;
}
}
}

View File

@@ -4,6 +4,6 @@ namespace ErsatzTV.Application.Emby;
public record UpdateEmbyPathReplacements(
int EmbyMediaSourceId,
List<EmbyPathReplacementItem> PathReplacements) : MediatR.IRequest<Either<BaseError, Unit>>;
List<EmbyPathReplacementItem> PathReplacements) : IRequest<Either<BaseError, Unit>>;
public record EmbyPathReplacementItem(int Id, string EmbyPath, string LocalPath);
public record EmbyPathReplacementItem(int Id, string EmbyPath, string LocalPath);

View File

@@ -4,7 +4,7 @@ using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Emby;
public class UpdateEmbyPathReplacementsHandler : MediatR.IRequestHandler<UpdateEmbyPathReplacements,
public class UpdateEmbyPathReplacementsHandler : IRequestHandler<UpdateEmbyPathReplacements,
Either<BaseError, Unit>>
{
private readonly IMediaSourceRepository _mediaSourceRepository;
@@ -46,4 +46,4 @@ public class UpdateEmbyPathReplacementsHandler : MediatR.IRequestHandler<UpdateE
.Map(
v => v.ToValidation<BaseError>(
$"Emby media source {request.EmbyMediaSourceId} does not exist."));
}
}

View File

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

View File

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

View File

@@ -5,4 +5,4 @@ namespace ErsatzTV.Application.Emby;
public record EmbyMediaSourceViewModel(int Id, string Name, string Address) : RemoteMediaSourceViewModel(
Id,
Name,
Address);
Address);

View File

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

View File

@@ -11,8 +11,8 @@ internal static class Mapper
embyMediaSource.Connections.HeadOrNone().Match(c => c.Address, string.Empty));
internal static EmbyLibraryViewModel ProjectToViewModel(EmbyLibrary library) =>
new(library.Id, library.Name, library.MediaKind, library.ShouldSyncItems);
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

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

View File

@@ -14,4 +14,4 @@ public class GetAllEmbyMediaSourcesHandler : IRequestHandler<GetAllEmbyMediaSour
GetAllEmbyMediaSources request,
CancellationToken cancellationToken) =>
_mediaSourceRepository.GetAllEmby().Map(list => list.Map(ProjectToViewModel).ToList());
}
}

View File

@@ -2,4 +2,4 @@
namespace ErsatzTV.Application.Emby;
public record GetEmbyConnectionParameters : IRequest<Either<BaseError, EmbyConnectionParametersViewModel>>;
public record GetEmbyConnectionParameters : IRequest<Either<BaseError, EmbyConnectionParametersViewModel>>;

View File

@@ -63,4 +63,4 @@ public class GetEmbyConnectionParametersHandler : IRequestHandler<GetEmbyConnect
private record ConnectionParameters(
EmbyMediaSource EmbyMediaSource,
EmbyConnection ActiveConnection);
}
}

View File

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

View File

@@ -16,4 +16,4 @@ public class
CancellationToken cancellationToken) =>
_mediaSourceRepository.GetEmbyLibraries(request.EmbyMediaSourceId)
.Map(list => list.Map(ProjectToViewModel).ToList());
}
}

View File

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

View File

@@ -15,4 +15,4 @@ public class
GetEmbyMediaSourceById request,
CancellationToken cancellationToken) =>
_mediaSourceRepository.GetEmby(request.EmbyMediaSourceId).MapT(ProjectToViewModel);
}
}

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