├── .gitattributes ├── .gitignore ├── README.markdown ├── mediasystem-api-datasource ├── pom.xml └── src │ └── main │ └── java │ └── hs │ └── mediasystem │ └── api │ └── datasource │ ├── WorkDescriptor.java │ ├── domain │ ├── AbstractCollection.java │ ├── Classification.java │ ├── CollectionDetails.java │ ├── Details.java │ ├── Episode.java │ ├── Folder.java │ ├── Identification.java │ ├── Keyword.java │ ├── Movie.java │ ├── Person.java │ ├── PersonRole.java │ ├── PersonalProfile.java │ ├── Production.java │ ├── ProductionCollection.java │ ├── ProductionRole.java │ ├── Release.java │ ├── Role.java │ ├── Serie.java │ └── stream │ │ ├── Contribution.java │ │ ├── Participation.java │ │ ├── Person.java │ │ ├── Recommendation.java │ │ └── Work.java │ └── services │ ├── IdentificationProvider.java │ ├── MinimalWorkSupport.java │ ├── PersonalProfileQueryService.java │ ├── QueryService.java │ ├── RecommendationQueryService.java │ ├── RolesQueryService.java │ ├── Top100QueryService.java │ └── VideoLinksQueryService.java ├── mediasystem-api-discovery ├── pom.xml └── src │ └── main │ └── java │ └── hs │ └── mediasystem │ └── api │ └── discovery │ ├── Attribute.java │ ├── Discoverer.java │ └── Discovery.java ├── mediasystem-db ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── hs │ │ │ └── mediasystem │ │ │ └── db │ │ │ ├── DatabaseFactory.java │ │ │ ├── DatabaseStatementTranslator.java │ │ │ ├── DatabaseUpdateException.java │ │ │ ├── DatabaseUpdater.java │ │ │ ├── PluginInitializer.java │ │ │ ├── ResponseCacheInitializer.java │ │ │ ├── ServiceRunner.java │ │ │ ├── SimpleDatabaseStatementTranslator.java │ │ │ ├── base │ │ │ ├── BasicDataTypesModule.java │ │ │ ├── DatabaseContentPrintProvider.java │ │ │ ├── DatabaseResponseCache.java │ │ │ ├── ImageDatabase.java │ │ │ ├── ImageRecord.java │ │ │ ├── MediaSystemDomainModule.java │ │ │ ├── Setting.java │ │ │ ├── SettingsSourceFactory.java │ │ │ ├── SettingsStore.java │ │ │ ├── StreamState.java │ │ │ ├── StreamStateProvider.java │ │ │ ├── StreamStateRecord.java │ │ │ ├── StreamStateService.java │ │ │ └── StreamStateStore.java │ │ │ ├── contentprints │ │ │ ├── ContentPrintDatabase.java │ │ │ └── ContentPrintRecord.java │ │ │ ├── core │ │ │ ├── DescriptorService.java │ │ │ ├── DiscoverEvent.java │ │ │ ├── DiscoveryController.java │ │ │ ├── IdentificationStore.java │ │ │ ├── IdentificationTaskManager.java │ │ │ ├── ImportSource.java │ │ │ ├── LinkedWorksService.java │ │ │ ├── MediaStreamService.java │ │ │ ├── ResourceEvent.java │ │ │ ├── ResourceService.java │ │ │ ├── StreamDescriptorFetchTaskManager.java │ │ │ ├── StreamableEvent.java │ │ │ ├── StreamableService.java │ │ │ └── domain │ │ │ │ ├── ContentPrint.java │ │ │ │ ├── LinkedWork.java │ │ │ │ ├── Resource.java │ │ │ │ ├── StreamTags.java │ │ │ │ └── Streamable.java │ │ │ ├── events │ │ │ ├── Serializer.java │ │ │ └── SerializerException.java │ │ │ ├── extract │ │ │ ├── StreamDescriptorFactory.java │ │ │ ├── StreamDescriptorService.java │ │ │ ├── StreamDescriptorStore.java │ │ │ └── grabber │ │ │ │ └── FFmpegFrameGrabber.java │ │ │ ├── jackson │ │ │ ├── RecordSerializer.java │ │ │ ├── SealedClassesModule.java │ │ │ └── SealedTypeSerializer.java │ │ │ ├── services │ │ │ ├── CollectionService.java │ │ │ ├── ImageService.java │ │ │ ├── LocalWorkService.java │ │ │ ├── PersonService.java │ │ │ ├── RecommendationService.java │ │ │ ├── WorkService.java │ │ │ ├── WorksService.java │ │ │ └── collection │ │ │ │ └── CollectionLocationManager.java │ │ │ └── uris │ │ │ ├── UriDatabase.java │ │ │ └── UriRecord.java │ └── resources │ │ └── db-scripts │ │ ├── db-v0.1.sql │ │ ├── db-v0.10.sql │ │ ├── db-v0.11.sql │ │ ├── db-v0.12.sql │ │ ├── db-v0.13.sql │ │ ├── db-v0.14.sql │ │ ├── db-v0.15.sql │ │ ├── db-v0.16.sql │ │ ├── db-v0.17.sql │ │ ├── db-v0.18.sql │ │ ├── db-v0.19.sql │ │ ├── db-v0.2.sql │ │ ├── db-v0.3.sql │ │ ├── db-v0.4.sql │ │ ├── db-v0.5.sql │ │ ├── db-v0.6.sql │ │ ├── db-v0.7.sql │ │ ├── db-v0.8.sql │ │ ├── db-v0.9.sql │ │ ├── db-v1.0.sql │ │ ├── db-v1.1.sql │ │ ├── db-v1.2.sql │ │ ├── db-v1.3.sql │ │ └── db-v1.4.sql │ └── test │ ├── java │ └── hs │ │ └── mediasystem │ │ ├── db │ │ ├── ArchitectureTest.java │ │ ├── base │ │ │ ├── DatabaseContentPrintProviderTest.java │ │ │ └── StreamStateStoreTest.java │ │ ├── core │ │ │ ├── IdentificationTaskManagerTest.java │ │ │ ├── StreamableServiceTest.java │ │ │ └── WorksServiceIT.java │ │ ├── services │ │ │ └── RecommendationServiceIT.java │ │ └── util │ │ │ ├── Episodes.java │ │ │ ├── InjectorExtension.java │ │ │ ├── Movies.java │ │ │ ├── Productions.java │ │ │ └── Series.java │ │ └── skipscan │ │ └── db │ │ └── DatabaseConfig.java │ └── resources │ └── testdata │ ├── mediasystem-collections.yaml │ ├── mediasystem-imports.yaml │ ├── movies │ ├── Avatar.txt │ ├── Matrix.txt │ └── Terminator.txt │ └── series │ └── Friends │ ├── friends_1x01.txt │ └── friends_1x02.txt ├── mediasystem-domain ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── hs │ └── mediasystem │ └── domain │ ├── media │ ├── AudioTrack.java │ ├── MediaStream.java │ ├── MediaStructure.java │ ├── Resolution.java │ ├── Snapshot.java │ ├── StreamDescriptor.java │ ├── SubtitleTrack.java │ └── VideoTrack.java │ ├── stream │ ├── ContentID.java │ └── MediaType.java │ └── work │ ├── AbstractId.java │ ├── Collection.java │ ├── CollectionDefinition.java │ ├── CollectionId.java │ ├── Context.java │ ├── DataSource.java │ ├── KeywordId.java │ ├── Match.java │ ├── PersonId.java │ ├── Reception.java │ ├── RoleId.java │ ├── State.java │ ├── VideoLink.java │ └── WorkId.java ├── mediasystem-ext-local ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── hs │ └── mediasystem │ └── ext │ └── local │ ├── Description.java │ ├── DescriptionService.java │ └── LocalIdentificationProvider.java ├── mediasystem-ext-mpv ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── hs │ │ └── mediasystem │ │ └── ext │ │ └── mpv │ │ ├── MPV.java │ │ ├── MPVPlayer.java │ │ └── NativeWindowMPVPlayerFactory.java │ └── resources │ └── win32-x86-64 │ └── mpv.dll ├── mediasystem-ext-scanners ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── hs │ │ └── mediasystem │ │ └── ext │ │ └── scanners │ │ ├── Constants.java │ │ ├── FoldersDiscoverer.java │ │ ├── MoviesDiscoverer.java │ │ ├── NameDecoder.java │ │ ├── PathFinder.java │ │ ├── Paths.java │ │ └── SeriesDiscoverer.java │ └── test │ ├── java │ └── hs │ │ └── mediasystem │ │ └── ext │ │ └── scanners │ │ └── NameDecoderTest.java │ └── resources │ └── Movies │ ├── 2012 [2009].mkv │ ├── A-team, The [2010, 1080p].mkv │ ├── Alice [(1461312), Fantasy, 720p].avi │ └── Underworld - 03 - Rise of the Lycans [2009, Action Fantasy Horror Thriller, 1080p] - Copy.mkv ├── mediasystem-ext-tmdb ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── hs │ │ └── mediasystem │ │ └── ext │ │ └── tmdb │ │ ├── DataSources.java │ │ ├── Genres.java │ │ ├── ObjectFactory.java │ │ ├── PersonRoles.java │ │ ├── TextMatcher.java │ │ ├── TheMovieDatabase.java │ │ ├── TmdbIdentificationProvider.java │ │ ├── TmdbPersonalProfileQueryService.java │ │ ├── TmdbQueryService.java │ │ ├── VideoLinks.java │ │ ├── identifier │ │ ├── EpisodeIdentifier.java │ │ ├── MovieIdentifier.java │ │ └── SerieIdentifier.java │ │ ├── movie │ │ ├── TmdbRecommendationQueryService.java │ │ ├── TmdbRolesQueryService.java │ │ ├── TmdbTop100QueryService.java │ │ └── TmdbVideoLinksQueryService.java │ │ └── provider │ │ ├── CollectionProvider.java │ │ ├── EpisodeProvider.java │ │ ├── MediaProvider.java │ │ ├── MovieProvider.java │ │ └── SerieProvider.java │ └── test │ ├── java │ └── hs │ │ └── mediasystem │ │ └── ext │ │ └── tmdb │ │ ├── TmdbIdentificationIT.java │ │ └── TmdbIdentificationServiceTest.java │ └── resources │ └── wiremock │ └── __files │ ├── tmdb-movie-218.json │ ├── tmdb-search-movie-terminator.json │ └── tmdb-tv-1981-season[0-8].json ├── mediasystem-ext-vlc ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── hs │ └── mediasystem │ └── ext │ └── vlc │ ├── AbstractVLCPlayerFactory.java │ ├── DeferredComponentIdVideoSurface.java │ ├── FXCanvasVLCPlayerFactory.java │ ├── NativeWindowVLCPlayerFactory.java │ ├── VLCPlayer.java │ └── util │ ├── Accessor.java │ ├── BeanAccessor.java │ └── BeanBooleanProperty.java ├── mediasystem-jfx ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ ├── com │ │ └── sun │ │ │ └── javafx │ │ │ └── binding │ │ │ ├── FilteredBinding.java │ │ │ └── ThrottledBinding.java │ │ └── javafx │ │ ├── beans │ │ └── value │ │ │ ├── ObservableValue.java │ │ │ ├── Throttler.java │ │ │ └── Throttlers.java │ │ └── throttle │ │ └── FXThrottlers.java │ └── test │ └── java │ ├── com │ └── sun │ │ └── javafx │ │ └── binding │ │ └── ThrottledBindingTest.java │ └── javafx │ ├── beans │ └── value │ │ ├── ObservableValueFluentBindingsTest.java │ │ ├── ObservableValueWhenTest.java │ │ └── ReferenceAsserts.java │ └── util │ └── ReplaceCamelCaseDisplayNameGenerator.java ├── mediasystem-local-client ├── .gitignore ├── logging-sample.properties ├── pom.xml └── src │ └── main │ └── java │ └── hs │ └── mediasystem │ └── local │ └── client │ ├── FrontEndRunner.java │ ├── HashUpdater.java │ ├── NonJavaFXFrontEndRunner.java │ └── service │ ├── LocalCollectionClient.java │ ├── LocalImageClient.java │ ├── LocalPersonClient.java │ ├── LocalRecommendationClient.java │ ├── LocalSettingsClient.java │ ├── LocalStreamStateClient.java │ ├── LocalWorkClient.java │ └── LocalWorksClient.java ├── mediasystem-package ├── .gitignore ├── pom.xml └── src │ └── main │ ├── assembly │ └── zip.xml │ └── resources │ ├── logging.properties │ └── scripts │ └── run.sh ├── mediasystem-runner ├── .gitignore ├── Documentation │ ├── About presentations.md │ ├── DB.graphml │ ├── Naming.md │ └── Safe-Bindings.graphml ├── pom.xml └── src │ ├── main │ ├── java │ │ ├── NoModule.java │ │ └── hs │ │ │ └── mediasystem │ │ │ ├── plugin │ │ │ ├── basictheme │ │ │ │ ├── AbstractPlacer.java │ │ │ │ ├── BasicTheme.java │ │ │ │ ├── ContributionsPlacer.java │ │ │ │ ├── FolderViewPlacer.java │ │ │ │ ├── GenericCollectionPlacer.java │ │ │ │ ├── ParticipationsPlacer.java │ │ │ │ ├── ProductionOverviewPlacer.java │ │ │ │ └── RecommendationsPlacer.java │ │ │ ├── cell │ │ │ │ ├── AnnotatedImageCellFactory.java │ │ │ │ ├── MediaGridViewCellFactory.java │ │ │ │ ├── annotated-image-cell-factory.less │ │ │ │ └── media-grid-cell-factory.less │ │ │ ├── home │ │ │ │ ├── AbstractCarouselNodeFactory.java │ │ │ │ ├── CollectionPresentationProvider.java │ │ │ │ ├── CollectionsNodeFactory.java │ │ │ │ ├── CollectionsPresentation.java │ │ │ │ ├── FoldersCollectionType.java │ │ │ │ ├── GeneralOptionsNodeFactory.java │ │ │ │ ├── GeneralOptionsPresentation.java │ │ │ │ ├── HomePresentation.java │ │ │ │ ├── HomeScreenNodeFactory.java │ │ │ │ ├── HorizontalCarousel.java │ │ │ │ ├── NewItemsNodeFactory.java │ │ │ │ ├── NewItemsPresentation.java │ │ │ │ ├── RecommendationsNodeFactory.java │ │ │ │ ├── RecommendationsPresentation.java │ │ │ │ ├── exit-styles.less │ │ │ │ ├── exit.jpg │ │ │ │ ├── help.jpg │ │ │ │ ├── options-backdrop.jpg │ │ │ │ └── styles.less │ │ │ ├── library │ │ │ │ └── scene │ │ │ │ │ ├── base │ │ │ │ │ ├── BackgroundPane.java │ │ │ │ │ ├── ContextLayout.java │ │ │ │ │ ├── LibraryNodeFactory.java │ │ │ │ │ ├── LibraryPresentation.java │ │ │ │ │ ├── curtain.jpg │ │ │ │ │ └── styles.less │ │ │ │ │ ├── grid │ │ │ │ │ ├── FolderPresentationFactory.java │ │ │ │ │ ├── FolderSetup.java │ │ │ │ │ ├── ProductionCollectionFactory.java │ │ │ │ │ ├── ProductionCollectionFactory.yaml │ │ │ │ │ ├── common │ │ │ │ │ │ ├── AbstractSetup.java │ │ │ │ │ │ ├── GridViewPresentationFactory.java │ │ │ │ │ │ ├── GridViewPresentationFactory.yaml │ │ │ │ │ │ ├── ViewStatusBarFactory.java │ │ │ │ │ │ ├── WorkCellPresentation.java │ │ │ │ │ │ ├── WorkCellPresentation.yaml │ │ │ │ │ │ ├── WorkNotFoundException.java │ │ │ │ │ │ ├── status-bar.less │ │ │ │ │ │ └── styles.less │ │ │ │ │ ├── contribution │ │ │ │ │ │ ├── ContributionsPresentationFactory.java │ │ │ │ │ │ ├── ContributionsPresentationFactory.yaml │ │ │ │ │ │ └── ContributionsSetup.java │ │ │ │ │ ├── folder-scene-styles.less │ │ │ │ │ ├── generic │ │ │ │ │ │ ├── GenericCollectionPresentationFactory.java │ │ │ │ │ │ ├── GenericCollectionPresentationFactory.yaml │ │ │ │ │ │ └── GenericCollectionSetup.java │ │ │ │ │ ├── participation │ │ │ │ │ │ ├── ConsolidatedParticipation.java │ │ │ │ │ │ ├── ParticipationsPresentationFactory.java │ │ │ │ │ │ ├── ParticipationsPresentationFactory.yaml │ │ │ │ │ │ ├── ParticipationsSetup.java │ │ │ │ │ │ └── PersonNotFoundException.java │ │ │ │ │ └── recommendation │ │ │ │ │ │ ├── RecommendationsPresentationFactory.java │ │ │ │ │ │ ├── RecommendationsPresentationFactory.yaml │ │ │ │ │ │ └── RecommendationsSetup.java │ │ │ │ │ └── overview │ │ │ │ │ ├── DetailAndCastPane.java │ │ │ │ │ ├── EpisodeListPane.java │ │ │ │ │ ├── EpisodePane.java │ │ │ │ │ ├── EpisodePresentation.java │ │ │ │ │ ├── EpisodePresentation.yaml │ │ │ │ │ ├── NavigationButtonsFactory.java │ │ │ │ │ ├── NavigationDialogButtonSkin.java │ │ │ │ │ ├── ProductionOverviewNodeFactory.java │ │ │ │ │ ├── ProductionOverviewPane.java │ │ │ │ │ ├── ProductionPresentationFactory.java │ │ │ │ │ ├── ProductionPresentationFactory.yaml │ │ │ │ │ ├── SeasonBar.java │ │ │ │ │ ├── ShowInfoEventHandler.java │ │ │ │ │ ├── play-dialog.less │ │ │ │ │ ├── show-info-styles.less │ │ │ │ │ └── styles.less │ │ │ ├── movies │ │ │ │ └── menu │ │ │ │ │ └── MoviesCollectionType.java │ │ │ ├── playback │ │ │ │ └── scene │ │ │ │ │ ├── MissingPlayerPresentationException.java │ │ │ │ │ ├── PlaybackInfoBorders.java │ │ │ │ │ ├── PlaybackLayout.java │ │ │ │ │ ├── PlaybackOverlayPane.java │ │ │ │ │ ├── PlaybackOverlayPresentation.java │ │ │ │ │ ├── PlaybackOverlayPresentation.yaml │ │ │ │ │ ├── PlayerBindings.java │ │ │ │ │ ├── PlayerSetting.java │ │ │ │ │ ├── VideoUnavailableException.java │ │ │ │ │ └── styles.less │ │ │ └── series │ │ │ │ └── menu │ │ │ │ └── SeriesCollectionType.java │ │ │ ├── presentation │ │ │ ├── AbstractPresentation.java │ │ │ ├── Navigable.java │ │ │ ├── Navigable.yaml │ │ │ ├── NavigateEvent.java │ │ │ ├── NodeFactory.java │ │ │ ├── ParentPresentation.java │ │ │ ├── Placer.java │ │ │ ├── PlacerQualifier.java │ │ │ ├── Presentation.java │ │ │ ├── PresentationActionEvent.java │ │ │ ├── PresentationActionFiredEvent.java │ │ │ ├── PresentationEvent.java │ │ │ └── Theme.java │ │ │ └── runner │ │ │ ├── Configuration.java │ │ │ ├── EventRoot.java │ │ │ ├── PluginBase.java │ │ │ ├── RootPresentationHandler.java │ │ │ ├── StartupPresentationProvider.java │ │ │ ├── action │ │ │ ├── ActionTargetProvider.java │ │ │ ├── ContextMenuHandler.java │ │ │ ├── FXControlFactory.java │ │ │ ├── InputActionHandler.java │ │ │ └── option-menu-dialog.less │ │ │ ├── collection │ │ │ └── CollectionType.java │ │ │ ├── config │ │ │ ├── BasicSetup.java │ │ │ ├── ConfigurationProvider.java │ │ │ └── LoggingConfigurer.java │ │ │ ├── dialog │ │ │ ├── DialogPane.java │ │ │ ├── Dialogs.java │ │ │ ├── Tasks.java │ │ │ └── dialogs.less │ │ │ ├── expose │ │ │ └── Annotations.java │ │ │ ├── fonts-linux.less │ │ │ ├── fonts.less │ │ │ ├── grouping │ │ │ ├── AlphabeticalGrouping.java │ │ │ ├── CollectionGrouping.java │ │ │ ├── GenreGrouping.java │ │ │ ├── GenreGrouping.yaml │ │ │ ├── Grouping.java │ │ │ ├── NoGrouping.java │ │ │ └── WorksGroup.java │ │ │ ├── media-look.less │ │ │ ├── presentation │ │ │ ├── PresentationLoader.java │ │ │ ├── Presentations.java │ │ │ ├── ViewPort.java │ │ │ └── ViewPortFactory.java │ │ │ ├── properties.less │ │ │ ├── root │ │ │ ├── ParentalControlsProvider.java │ │ │ ├── RootNodeFactory.java │ │ │ ├── RootPresentation.java │ │ │ ├── RootPresentation.yaml │ │ │ ├── action-display-popup.less │ │ │ ├── clock-pane.less │ │ │ ├── fps-pane.less │ │ │ ├── logo-pane.less │ │ │ ├── progress-pane.less │ │ │ └── root.less │ │ │ └── util │ │ │ ├── DefaultPlayerWindowIdSupplier.java │ │ │ ├── DelegatingComparator.java │ │ │ ├── FXSceneManager.java │ │ │ ├── LessLoader.java │ │ │ ├── LocalImageURIHandler.java │ │ │ ├── MarkdownTextView.java │ │ │ ├── MultiImageURIHandler.java │ │ │ ├── SceneManager.java │ │ │ ├── action │ │ │ ├── Action.java │ │ │ └── ActionTarget.java │ │ │ ├── debug │ │ │ ├── DebugFX.java │ │ │ └── DebugSceneFX.java │ │ │ ├── global.less │ │ │ ├── grid │ │ │ ├── MediaGridView.java │ │ │ ├── MediaItemFormatter.java │ │ │ ├── MediaStatus.java │ │ │ └── media-grid-view.less │ │ │ ├── markdown-styles.less │ │ │ └── resource │ │ │ └── ResourceManager.java │ └── resources │ │ └── help.markdown │ └── test │ └── java │ └── hs │ └── mediasystem │ ├── ArchitectureTest.java │ ├── plugin │ └── library │ │ └── scene │ │ └── grid │ │ └── common │ │ └── GridViewPresentationFactoryTest.java │ └── runner │ └── action │ └── ActionTargetProviderTest.java ├── mediasystem-ui-api ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── hs │ └── mediasystem │ └── ui │ └── api │ ├── CollectionClient.java │ ├── ConsumedStateChanged.java │ ├── ImageClient.java │ ├── PersonClient.java │ ├── RecommendationClient.java │ ├── SettingsClient.java │ ├── SettingsSource.java │ ├── StreamStateClient.java │ ├── WorkClient.java │ ├── WorksClient.java │ ├── domain │ ├── Classification.java │ ├── Context.java │ ├── Contribution.java │ ├── Details.java │ ├── MediaStream.java │ ├── Participation.java │ ├── Person.java │ ├── Recommendation.java │ ├── Role.java │ ├── Sequence.java │ ├── Serie.java │ ├── Stage.java │ ├── State.java │ └── Work.java │ └── player │ ├── AudioTrack.java │ ├── NativePlayerInitializationException.java │ ├── PlayerEvent.java │ ├── PlayerFactory.java │ ├── PlayerPresentation.java │ ├── PlayerPresentation.yaml │ ├── PlayerWindowIdSupplier.java │ ├── StatOverlay.java │ ├── Subtitle.java │ ├── SubtitlePresentation.java │ └── SubtitlePresentation.yaml ├── mediasystem-util ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── hs │ │ └── mediasystem │ │ └── util │ │ ├── Attributes.java │ │ ├── AutoReentrantLock.java │ │ ├── BeanUtils.java │ │ ├── Bytes.java │ │ ├── CryptoUtil.java │ │ ├── Localizable.java │ │ ├── MediaHash.java │ │ ├── PriorityRateLimiter.java │ │ ├── RateLimiter.java │ │ ├── bg │ │ └── BackgroundTaskRegistry.java │ │ ├── checked │ │ ├── CheckedOptional.java │ │ ├── CheckedStreams.java │ │ ├── Flow.java │ │ ├── Stream0.java │ │ ├── Stream1.java │ │ ├── Stream2.java │ │ ├── Stream3.java │ │ ├── ThrowingConsumer.java │ │ ├── ThrowingFunction.java │ │ ├── ThrowingPredicate.java │ │ └── ThrowingSupplier.java │ │ ├── concurrent │ │ ├── AutoSemaphore.java │ │ ├── NamedExecutors.java │ │ └── NamedThreadFactory.java │ │ ├── domain │ │ ├── Tuple.java │ │ ├── URIs.java │ │ └── URLs.java │ │ ├── exception │ │ ├── Exceptional.java │ │ ├── ExceptionalException.java │ │ ├── HttpException.java │ │ ├── Throwables.java │ │ ├── TryRunnable.java │ │ ├── TrySupplier.java │ │ └── WrappedCheckedException.java │ │ ├── expose │ │ ├── AbstractExposedControl.java │ │ ├── AbstractExposedNumericProperty.java │ │ ├── AbstractExposedProperty.java │ │ ├── Expose.java │ │ ├── ExposedBooleanProperty.java │ │ ├── ExposedControl.java │ │ ├── ExposedDoubleProperty.java │ │ ├── ExposedListProperty.java │ │ ├── ExposedLongProperty.java │ │ ├── ExposedMethod.java │ │ ├── ExposedNode.java │ │ ├── ExposedNumberProperty.java │ │ ├── Formatter.java │ │ └── Trigger.java │ │ ├── image │ │ ├── HttpImageURIHandler.java │ │ ├── ImageHandle.java │ │ ├── ImageHandleFactory.java │ │ ├── ImageURI.java │ │ ├── ImageURIHandler.java │ │ └── ResourceImageHandle.java │ │ ├── ini │ │ ├── Ini.java │ │ └── Section.java │ │ ├── javafx │ │ ├── AsyncImageProperty.java │ │ ├── CancellableCompletableFuture.java │ │ ├── DebugTooltip.java │ │ ├── ImageCache.java │ │ ├── NestedTimeTracker.java │ │ ├── Properties.java │ │ ├── SceneUtil.java │ │ ├── SpecialEffects.java │ │ ├── action │ │ │ ├── Action.java │ │ │ └── SimpleAction.java │ │ ├── base │ │ │ ├── EventHandlerTarget.java │ │ │ ├── Events.java │ │ │ ├── FocusEvent.java │ │ │ ├── ItemSelectedEvent.java │ │ │ └── Nodes.java │ │ ├── control │ │ │ ├── AbstractImageView.java │ │ │ ├── ActionListView.java │ │ │ ├── AreaPane2.java │ │ │ ├── AutoVerticalScrollPane.java │ │ │ ├── BiasedImageView.java │ │ │ ├── Buttons.java │ │ │ ├── Containers.java │ │ │ ├── GridPane.java │ │ │ ├── GridPaneUtil.java │ │ │ ├── Labels.java │ │ │ ├── MinimalScrollBarSkin.java │ │ │ ├── MultiButton.java │ │ │ ├── RangeBar.java │ │ │ ├── ResizableWritableImageView.java │ │ │ ├── StarRating.java │ │ │ ├── TablePane.java │ │ │ ├── VerticalLabel.java │ │ │ ├── VerticalLabels.java │ │ │ ├── ZoomImageView.java │ │ │ └── range-bar.css │ │ ├── property │ │ │ └── SimpleReadOnlyObjectProperty.java │ │ └── ui │ │ │ ├── carousel │ │ │ ├── AbstractCellIterator.java │ │ │ ├── AbstractHorizontalCellIterator.java │ │ │ ├── CarouselListCell.java │ │ │ ├── CarouselSkin.java │ │ │ ├── CellIterator.java │ │ │ ├── CellPool.java │ │ │ ├── DebugControlPanel.java │ │ │ ├── Layout.java │ │ │ ├── LinearCellIterator.java │ │ │ ├── LinearLayout.java │ │ │ ├── RayCellIterator.java │ │ │ └── RayLayout.java │ │ │ ├── csslayout │ │ │ ├── CssLayoutFactory.java │ │ │ ├── Resolvable.java │ │ │ ├── StylableContainers.java │ │ │ ├── StylableHBox.java │ │ │ ├── StylableStackPane.java │ │ │ └── StylableVBox.java │ │ │ ├── gridlistviewskin │ │ │ ├── GridListViewSkin.java │ │ │ ├── Group.java │ │ │ └── GroupManager.java │ │ │ ├── status │ │ │ ├── StatusIndicator.java │ │ │ └── styles.css │ │ │ └── transition │ │ │ ├── StandardTransitions.java │ │ │ ├── TransitionPane.java │ │ │ ├── domain │ │ │ ├── EffectList.java │ │ │ ├── MultiNodeTransition.java │ │ │ └── TransitionEffect.java │ │ │ ├── effects │ │ │ ├── Fade.java │ │ │ └── Slide.java │ │ │ └── multi │ │ │ ├── Custom.java │ │ │ └── Scroll.java │ │ ├── junit │ │ └── ReplaceCamelCaseDisplayNameGenerator.java │ │ ├── logging │ │ ├── ClearLoggingFormatter.java │ │ ├── DateTimeLoggingFormatter.java │ │ ├── FileHandler1.java │ │ ├── FileHandler2.java │ │ ├── PathCreatingFileHandler.java │ │ └── TimeLoggingFormatter.java │ │ ├── natural │ │ ├── Levenshtein.java │ │ ├── NaturalLanguage.java │ │ ├── SizeFormatter.java │ │ └── WeightedNgramDistance.java │ │ ├── parser │ │ ├── CssStyle.java │ │ ├── Cursor.java │ │ ├── JavaStyle.java │ │ ├── Parser.java │ │ ├── Token.java │ │ └── Type.java │ │ ├── ref │ │ └── Cache.java │ │ └── time │ │ ├── SimulatedTimeSource.java │ │ ├── SystemTimeSource.java │ │ └── TimeSource.java │ └── test │ └── java │ └── hs │ └── mediasystem │ └── util │ ├── ArchitectureTest.java │ ├── BytesTest.java │ ├── PostConstructCaller.java │ ├── PriorityRateLimiterTest.java │ ├── RateLimiterTest.java │ ├── checked │ ├── CheckedOptionalTest.java │ ├── CheckedStreamsTest.java │ └── FlowTest.java │ ├── domain │ └── URIsTest.java │ ├── exception │ └── ExceptionalTest.java │ ├── javafx │ ├── CancellableCompletableFutureTest.java │ ├── ImageCacheTest.java │ ├── NestedTimeTrackerTest.java │ ├── control │ │ ├── BiasedImageViewTest.java │ │ └── ZoomImageViewTest.java │ └── ui │ │ ├── carousel │ │ └── LinearLayoutTest.java │ │ └── gridlistviewskin │ │ └── GroupManagerTest.java │ └── natural │ ├── LevenshteinTest.java │ ├── SizeFormatterTest.java │ ├── WeightedNgramDistanceTest.java │ └── WeightedNgramDistanceTest2.java ├── pom.xml ├── screenshot-1.jpg ├── screenshot-2.jpg ├── screenshot-3.jpg └── screenshot-4.jpg /.gitattributes: -------------------------------------------------------------------------------- 1 | *.jpg filter=lfs diff=lfs merge=lfs -text 2 | *.png filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .settings/ 3 | .classpath 4 | .project 5 | bin/ -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/WorkDescriptor.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource; 2 | 3 | import hs.mediasystem.api.datasource.domain.Details; 4 | import hs.mediasystem.domain.work.WorkId; 5 | 6 | /** 7 | * Describes a work (as in a work of art). 8 | */ 9 | public interface WorkDescriptor { // TODO rename this one to Work? 10 | WorkId getId(); 11 | Details getDetails(); 12 | 13 | default WorkId id() { 14 | return getId(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/domain/Classification.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.domain; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public record Classification(List genres, List languages, List keywords, Map contentRatings, Boolean pornographic) { 7 | public static final Classification EMPTY = new Classification(List.of(), List.of(), List.of(), Map.of(), null); 8 | 9 | public Classification { 10 | if(genres == null) { 11 | throw new IllegalArgumentException("genres cannot be null"); 12 | } 13 | if(languages == null) { 14 | throw new IllegalArgumentException("languages cannot be null"); 15 | } 16 | if(keywords == null) { 17 | throw new IllegalArgumentException("keywords cannot be null"); 18 | } 19 | if(contentRatings == null) { 20 | throw new IllegalArgumentException("contentRatings cannot be null"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/domain/CollectionDetails.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.domain; 2 | 3 | import hs.mediasystem.domain.work.WorkId; 4 | 5 | public class CollectionDetails { 6 | private final WorkId id; 7 | private final Details details; 8 | 9 | public CollectionDetails(WorkId id, Details details) { 10 | if(id == null) { 11 | throw new IllegalArgumentException("id cannot be null"); 12 | } 13 | if(details == null) { 14 | throw new IllegalArgumentException("details cannot be null"); 15 | } 16 | 17 | this.id = id; 18 | this.details = details; 19 | } 20 | 21 | public WorkId getId() { 22 | return id; 23 | } 24 | 25 | public Details getDetails() { 26 | return details; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/domain/Folder.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.domain; 2 | 3 | import hs.mediasystem.domain.work.Reception; 4 | import hs.mediasystem.domain.work.WorkId; 5 | 6 | public class Folder extends Production { 7 | 8 | /** 9 | * Constructs a new instance. 10 | * 11 | * @param id a {@link WorkId}, cannot be null 12 | * @param details a {@link Details}, cannot be null 13 | * @param reception a {@link Reception}, can be null 14 | * @param classification a {@link Classification}, cannot be null 15 | */ 16 | public Folder(WorkId id, Details details, Reception reception, Classification classification) { 17 | super(id, details, reception, null, null, classification, 1.0); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/domain/Identification.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.domain; 2 | 3 | import hs.mediasystem.domain.work.Match; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Holds the result of an identification. 9 | * 10 | * @param releases one or more releases that matched the discovery, cannot be {@code null} or empty 11 | * @param match a {@link Match}, cannot be {@code null} 12 | */ 13 | public record Identification(List releases, Match match) { 14 | public Identification { 15 | if(releases == null) { 16 | throw new IllegalArgumentException("releases cannot be null"); 17 | } 18 | if(match == null) { 19 | throw new IllegalArgumentException("match cannot be null"); 20 | } 21 | if(releases.isEmpty()) { 22 | throw new IllegalArgumentException("releases cannot be empty"); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/domain/Keyword.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.domain; 2 | 3 | import hs.mediasystem.domain.work.KeywordId; 4 | 5 | public class Keyword { 6 | private final KeywordId id; 7 | private final String text; 8 | 9 | public Keyword(KeywordId id, String text) { 10 | if(id == null) { 11 | throw new IllegalArgumentException("id cannot be null"); 12 | } 13 | if(text == null) { 14 | throw new IllegalArgumentException("text cannot be null"); 15 | } 16 | 17 | this.id = id; 18 | this.text = text; 19 | } 20 | 21 | public KeywordId getId() { 22 | return id; 23 | } 24 | 25 | public String getText() { 26 | return text; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/domain/Movie.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.domain; 2 | 3 | import hs.mediasystem.domain.work.Context; 4 | import hs.mediasystem.domain.work.Reception; 5 | import hs.mediasystem.domain.work.WorkId; 6 | 7 | import java.time.Duration; 8 | 9 | public class Movie extends Production { 10 | public enum State { 11 | PLANNED, 12 | IN_PRODUCTION, 13 | RELEASED 14 | } 15 | 16 | private final Duration runtime; 17 | private final State state; 18 | 19 | public Movie(WorkId id, Details details, Reception reception, Context context, String tagLine, Duration runtime, Classification classification, double popularity, State state) { 20 | super(id, details, reception, context, tagLine, classification, popularity); 21 | 22 | this.runtime = runtime; 23 | this.state = state; 24 | } 25 | 26 | public Duration getRuntime() { 27 | return runtime; 28 | } 29 | 30 | public State getState() { 31 | return state; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/domain/Person.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.domain; 2 | 3 | import hs.mediasystem.domain.work.PersonId; 4 | import hs.mediasystem.util.image.ImageURI; 5 | 6 | public class Person { 7 | private final PersonId id; 8 | private final String name; 9 | private final ImageURI cover; 10 | private final Details details; 11 | 12 | public Person(PersonId id, String name, ImageURI cover) { 13 | if(id == null) { 14 | throw new IllegalArgumentException("id cannot be null"); 15 | } 16 | if(name == null) { 17 | throw new IllegalArgumentException("name cannot be null"); 18 | } 19 | 20 | this.id = id; 21 | this.name = name; 22 | this.details = new Details(name, null, null, null, cover, null, null); 23 | this.cover = cover; 24 | } 25 | 26 | public PersonId getId() { 27 | return id; 28 | } 29 | 30 | public Details getDetails() { 31 | return details; 32 | } 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public ImageURI getCover() { 39 | return cover; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/domain/PersonRole.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.domain; 2 | 3 | import hs.mediasystem.domain.work.RoleId; 4 | 5 | public class PersonRole { 6 | private final Person person; 7 | private final Role role; 8 | private final double order; 9 | 10 | public PersonRole(Person person, Role role, double order) { 11 | if(person == null) { 12 | throw new IllegalArgumentException("person cannot be null"); 13 | } 14 | if(role == null) { 15 | throw new IllegalArgumentException("role cannot be null"); 16 | } 17 | 18 | this.person = person; 19 | this.role = role; 20 | this.order = order; 21 | } 22 | 23 | public RoleId getId() { 24 | return role.getId(); 25 | } 26 | 27 | public Details getDetails() { 28 | return person.getDetails(); 29 | } 30 | 31 | public Person getPerson() { 32 | return person; 33 | } 34 | 35 | public Role getRole() { 36 | return role; 37 | } 38 | 39 | public double getOrder() { 40 | return order; 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/domain/ProductionRole.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.domain; 2 | 3 | import hs.mediasystem.domain.work.RoleId; 4 | 5 | public class ProductionRole { 6 | private final Production production; 7 | private final Role role; 8 | private final Integer episodeCount; 9 | private final double popularity; 10 | 11 | public ProductionRole(Production production, Role role, Integer episodeCount, double popularity) { 12 | if(production == null) { 13 | throw new IllegalArgumentException("production cannot be null"); 14 | } 15 | if(role == null) { 16 | throw new IllegalArgumentException("role cannot be null"); 17 | } 18 | 19 | this.production = production; 20 | this.role = role; 21 | this.episodeCount = episodeCount; 22 | this.popularity = popularity; 23 | } 24 | 25 | public RoleId getId() { 26 | return role.getId(); 27 | } 28 | 29 | public Production getProduction() { 30 | return production; 31 | } 32 | 33 | public Role getRole() { 34 | return role; 35 | } 36 | 37 | public Integer getEpisodeCount() { 38 | return episodeCount; 39 | } 40 | 41 | public double getPopularity() { 42 | return popularity; 43 | } 44 | 45 | public Details getDetails() { 46 | return production.getDetails(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/domain/stream/Contribution.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.domain.stream; 2 | 3 | import hs.mediasystem.api.datasource.domain.Person; 4 | import hs.mediasystem.api.datasource.domain.Role; 5 | 6 | public class Contribution { 7 | private final Person person; 8 | private final Role role; 9 | private final double order; 10 | 11 | public Contribution(Person person, Role role, double order) { 12 | if(person == null) { 13 | throw new IllegalArgumentException("person cannot be null"); 14 | } 15 | if(role == null) { 16 | throw new IllegalArgumentException("role cannot be null"); 17 | } 18 | 19 | this.person = person; 20 | this.role = role; 21 | this.order = order; 22 | } 23 | 24 | public Person getPerson() { 25 | return person; 26 | } 27 | 28 | public Role getRole() { 29 | return role; 30 | } 31 | 32 | public double getOrder() { 33 | return order; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/domain/stream/Participation.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.domain.stream; 2 | 3 | import hs.mediasystem.api.datasource.domain.Role; 4 | 5 | public class Participation { 6 | private final Role role; 7 | private final Work work; 8 | private final int episodeCount; 9 | private final double popularity; 10 | 11 | public Participation(Role role, Work work, int episodeCount, double popularity) { 12 | this.role = role; 13 | this.work = work; 14 | this.episodeCount = episodeCount; 15 | this.popularity = popularity; 16 | } 17 | 18 | public Role getRole() { 19 | return role; 20 | } 21 | 22 | public Work getWork() { 23 | return work; 24 | } 25 | 26 | public int getEpisodeCount() { 27 | return episodeCount; 28 | } 29 | 30 | public double getPopularity() { 31 | return popularity; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/domain/stream/Recommendation.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.domain.stream; 2 | 3 | import java.time.Instant; 4 | 5 | /** 6 | * Recommends a {@link Work} based on another work or (a specific stream of) itself. 7 | * 8 | * @param instant an {@link Instant} indicating the relevant time for this recommendation, cannot be null 9 | * @param work a {@link Work} that is recommended, cannot be null 10 | */ 11 | public record Recommendation(Instant instant, Work work) { 12 | public Recommendation { 13 | if(instant == null) { 14 | throw new IllegalArgumentException("instant cannot be null"); 15 | } 16 | if(work == null) { 17 | throw new IllegalArgumentException("work cannot be null"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/services/PersonalProfileQueryService.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.services; 2 | 3 | import hs.mediasystem.api.datasource.domain.PersonalProfile; 4 | import hs.mediasystem.domain.work.PersonId; 5 | 6 | import java.io.IOException; 7 | import java.util.Optional; 8 | 9 | public interface PersonalProfileQueryService { 10 | Optional query(PersonId id) throws IOException; 11 | } 12 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/services/QueryService.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.services; 2 | 3 | import hs.mediasystem.api.datasource.WorkDescriptor; 4 | import hs.mediasystem.domain.work.DataSource; 5 | import hs.mediasystem.domain.work.WorkId; 6 | 7 | import java.io.IOException; 8 | import java.util.Optional; 9 | 10 | public interface QueryService { 11 | DataSource getDataSource(); 12 | 13 | /** 14 | * Queries a descriptor service and returns an optional {@link WorkDescriptor} or 15 | * empty if not found. An exception is thrown if the service could not be contacted 16 | * at all. 17 | * 18 | * @param id an {@link WorkId}, cannot be {@code null} 19 | * @return an optional {@link WorkDescriptor}, never {@code null} 20 | * @throws IOException when an I/O problem occurred 21 | */ 22 | Optional query(WorkId id) throws IOException; 23 | } 24 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/services/RecommendationQueryService.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.services; 2 | 3 | import hs.mediasystem.api.datasource.domain.Production; 4 | import hs.mediasystem.domain.work.WorkId; 5 | 6 | import java.io.IOException; 7 | import java.util.List; 8 | 9 | public interface RecommendationQueryService { 10 | 11 | /** 12 | * Gets recommendations for a Movie or Serie. 13 | * 14 | * @param id a {@link WorkId}, cannot be null 15 | * @return a list of recommended Productions, never null 16 | * @throws IOException when an I/O problem occurred 17 | */ 18 | List query(WorkId id) throws IOException; 19 | } 20 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/services/RolesQueryService.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.services; 2 | 3 | import hs.mediasystem.api.datasource.domain.PersonRole; 4 | import hs.mediasystem.domain.work.WorkId; 5 | 6 | import java.io.IOException; 7 | import java.util.List; 8 | 9 | public interface RolesQueryService { 10 | String getDataSourceName(); 11 | List query(WorkId id) throws IOException; 12 | } 13 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/services/Top100QueryService.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.services; 2 | 3 | import hs.mediasystem.api.datasource.domain.Production; 4 | 5 | import java.io.IOException; 6 | import java.util.List; 7 | 8 | public interface Top100QueryService { 9 | List query() throws IOException; 10 | } 11 | -------------------------------------------------------------------------------- /mediasystem-api-datasource/src/main/java/hs/mediasystem/api/datasource/services/VideoLinksQueryService.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.datasource.services; 2 | 3 | import hs.mediasystem.domain.work.VideoLink; 4 | import hs.mediasystem.domain.work.WorkId; 5 | 6 | import java.io.IOException; 7 | import java.util.List; 8 | 9 | public interface VideoLinksQueryService { 10 | List query(WorkId id) throws IOException; 11 | } 12 | -------------------------------------------------------------------------------- /mediasystem-api-discovery/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | hs.mediasystem 8 | mediasystem-parent 9 | ${revision} 10 | 11 | 12 | mediasystem-api-discovery 13 | 14 | 15 | 16 | hs.mediasystem 17 | mediasystem-domain 18 | ${revision} 19 | 20 | 21 | hs.mediasystem 22 | mediasystem-util 23 | ${revision} 24 | 25 | 26 | 27 | 28 | org.junit.jupiter 29 | junit-jupiter-engine 30 | test 31 | 32 | 33 | org.mockito 34 | mockito-core 35 | test 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /mediasystem-api-discovery/src/main/java/hs/mediasystem/api/discovery/Attribute.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.discovery; 2 | 3 | public interface Attribute { 4 | public static final String TITLE = "title"; 5 | public static final String ALTERNATIVE_TITLE = "alternativeTitle"; 6 | public static final String DESCRIPTION = "description"; 7 | public static final String SUBTITLE = "subtitle"; 8 | public static final String SEQUENCE = "sequence"; 9 | public static final String YEAR = "year"; 10 | public static final String ID_PREFIX = "id:"; 11 | public static final String CHILD_TYPE = "childType"; 12 | 13 | public enum ChildType { 14 | EPISODE, 15 | SPECIAL, 16 | EXTRA 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mediasystem-api-discovery/src/main/java/hs/mediasystem/api/discovery/Discoverer.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.api.discovery; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | import java.util.List; 6 | 7 | public interface Discoverer { 8 | 9 | interface Registry { 10 | 11 | /** 12 | * Register a list of discoveries for a location. 13 | * 14 | * @param parentLocation a parent location, cannot be {@code null} 15 | * @param discoveries a list of discoveries, cannot be {@code null}, but can be empty 16 | */ 17 | void register(URI parentLocation, List discoveries); 18 | } 19 | 20 | /** 21 | * Scans a given {@link URI} for potential discoveries. Discoveries must 22 | * be provided in breadth first order (a parent must be discovered before any 23 | * of its children) or they will be ignored. If this order is violated, it 24 | * may take multiple passes before all discoveries are processed. 25 | * 26 | * @param root a root, cannot be {@code null} 27 | * @param registry a place to register discoveries, cannot be {@code null} 28 | * @throws IOException when an I/O problem occurred 29 | */ 30 | void discover(URI root, Registry registry) throws IOException; 31 | } 32 | -------------------------------------------------------------------------------- /mediasystem-db/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/DatabaseStatementTranslator.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db; 2 | 3 | public interface DatabaseStatementTranslator { 4 | String translate(String statement); 5 | } 6 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/DatabaseUpdateException.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db; 2 | 3 | import java.sql.SQLException; 4 | 5 | public class DatabaseUpdateException extends SQLException { 6 | 7 | public DatabaseUpdateException(String message, Throwable cause) { 8 | super(message, cause); 9 | } 10 | 11 | public DatabaseUpdateException(String message) { 12 | super(message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/ResponseCacheInitializer.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db; 2 | 3 | import hs.mediasystem.db.base.DatabaseResponseCache; 4 | 5 | import java.net.ResponseCache; 6 | 7 | import javax.annotation.PostConstruct; 8 | import javax.inject.Inject; 9 | 10 | public class ResponseCacheInitializer { 11 | @Inject private DatabaseResponseCache databaseResponseCache; 12 | 13 | @PostConstruct 14 | private void postConstruct() { 15 | ResponseCache.setDefault(databaseResponseCache); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/ServiceRunner.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db; 2 | 3 | import org.int4.dirk.api.CandidateRegistry; 4 | import org.int4.dirk.api.Injector; 5 | import org.int4.dirk.plugins.ComponentScanner; 6 | import org.int4.dirk.plugins.ComponentScannerFactory; 7 | 8 | public class ServiceRunner { 9 | 10 | public static void start(Injector injector) { 11 | CandidateRegistry registry = injector.getCandidateRegistry(); 12 | 13 | ComponentScannerFactory componentScannerFactory = injector.getInstance(ComponentScannerFactory.class); 14 | ComponentScanner componentScanner = componentScannerFactory.create( 15 | "hs.mediasystem.db", 16 | "hs.mediasystem.util", 17 | "hs.mediasystem.mediamanager" 18 | ); 19 | 20 | componentScanner.scan(registry); 21 | 22 | injector.getInstance(ResponseCacheInitializer.class); // Triggers response cache setup 23 | injector.getInstance(PluginInitializer.class); // Triggers plugin initialisation 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/SimpleDatabaseStatementTranslator.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db; 2 | 3 | import java.util.Map; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | class SimpleDatabaseStatementTranslator implements DatabaseStatementTranslator { 8 | private final Map translations; 9 | 10 | private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\$\\{(\\w+)\\}"); 11 | 12 | public SimpleDatabaseStatementTranslator(Map translations) { 13 | this.translations = translations; 14 | } 15 | 16 | @Override 17 | public String translate(String statement) { 18 | StringBuffer sb = new StringBuffer(); 19 | Matcher matcher = VARIABLE_PATTERN.matcher(statement); 20 | 21 | while(matcher.find()) { 22 | matcher.appendReplacement(sb, translations.get(matcher.group(1))); 23 | } 24 | 25 | matcher.appendTail(sb); 26 | 27 | return sb.toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/base/BasicDataTypesModule.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.base; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.core.JsonParser; 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.DeserializationContext; 7 | import com.fasterxml.jackson.databind.JsonDeserializer; 8 | import com.fasterxml.jackson.databind.JsonSerializer; 9 | import com.fasterxml.jackson.databind.SerializerProvider; 10 | import com.fasterxml.jackson.databind.module.SimpleModule; 11 | 12 | import hs.mediasystem.domain.stream.ContentID; 13 | 14 | import java.io.IOException; 15 | 16 | public class BasicDataTypesModule extends SimpleModule { 17 | 18 | public BasicDataTypesModule() { 19 | addSerializer(ContentID.class, new JsonSerializer() { 20 | @Override 21 | public void serialize(ContentID value, JsonGenerator gen, SerializerProvider serializers) throws IOException { 22 | gen.writeNumber(value.asInt()); 23 | } 24 | }); 25 | 26 | addDeserializer(ContentID.class, new JsonDeserializer() { 27 | @Override 28 | public ContentID deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { 29 | return new ContentID(p.getIntValue()); 30 | } 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/base/ImageRecord.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.base; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public record ImageRecord(String url, String key, LocalDateTime creationTime, LocalDateTime accessTime, byte[] image) { 6 | 7 | public static ImageRecord of(String url, String key, byte[] data) { 8 | LocalDateTime now = LocalDateTime.now(); 9 | 10 | return new ImageRecord(url, key, now, now, data); 11 | } 12 | 13 | public ImageRecord with(String key, LocalDateTime creationTime, byte[] image) { 14 | return new ImageRecord(url, key, creationTime, accessTime, image); 15 | } 16 | 17 | public ImageRecord withAccessTime(LocalDateTime accessTime) { 18 | return new ImageRecord(url, key, creationTime, accessTime, image); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/base/Setting.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.base; 2 | 3 | import java.util.Date; 4 | 5 | public record Setting(Integer id, String system, PersistLevel persistLevel, String name, String value, Date lastUpdated) { 6 | public enum PersistLevel {PERMANENT, TEMPORARY, SESSION} 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/base/SettingsSourceFactory.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.base; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Singleton; 5 | 6 | @Singleton 7 | public class SettingsSourceFactory { 8 | @Inject private SettingsStore settingsStore; 9 | 10 | public SettingsSource of(String system) { 11 | return new SettingsSource(system); 12 | } 13 | 14 | public class SettingsSource { 15 | private final String system; 16 | 17 | private SettingsSource(String system) { 18 | this.system = system; 19 | } 20 | 21 | public String getSetting(String name) { 22 | return settingsStore.getSetting(system, name); 23 | } 24 | 25 | public String getSettingOrDefault(String name, String defaultValue) { 26 | return settingsStore.getSettingOrDefault(system, name, defaultValue); 27 | } 28 | 29 | public int getIntSettingOrDefault(String name, int defaultValue, int min, int max) { 30 | return settingsStore.getIntSettingOrDefault(system, name, defaultValue, min, max); 31 | } 32 | 33 | public void storeSetting(String name, String value) { 34 | settingsStore.storeSetting(system, name, value); 35 | } 36 | 37 | public void storeIntSetting(String name, int value) { 38 | settingsStore.storeIntSetting(system, name, value); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/base/StreamState.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.base; 2 | 3 | import hs.mediasystem.domain.stream.ContentID; 4 | 5 | import java.util.Map; 6 | 7 | public class StreamState { 8 | private final Map properties; 9 | private final ContentID contentId; 10 | 11 | public StreamState(ContentID contentId, Map properties) { 12 | this.contentId = contentId; 13 | this.properties = properties; 14 | } 15 | 16 | public ContentID getContentID() { 17 | return contentId; 18 | } 19 | 20 | public Map getProperties() { 21 | return properties; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/base/StreamStateRecord.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.base; 2 | 3 | public record StreamStateRecord(int contentId, byte[] json) {} 4 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/contentprints/ContentPrintRecord.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.contentprints; 2 | 3 | import java.time.Instant; 4 | 5 | public record ContentPrintRecord( 6 | Integer id, 7 | byte[] hash, 8 | Long size, 9 | long lastModificationTime, // in milliseconds since epoch 10 | Long lastSeenTime, // in milliseconds since epoch, if null was seen recently 11 | long creationMillis // signature creation millis 12 | ) { 13 | public ContentPrintRecord with(byte[] hash, Long size, Instant lastModificationTime) { 14 | return new ContentPrintRecord(id, hash, size, lastModificationTime.toEpochMilli(), lastSeenTime, creationMillis); 15 | } 16 | 17 | public ContentPrintRecord withId(int id) { 18 | return new ContentPrintRecord(id, hash, size, lastModificationTime, lastSeenTime, creationMillis); 19 | } 20 | } -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/core/DiscoverEvent.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.core; 2 | 3 | import hs.mediasystem.api.datasource.services.IdentificationProvider; 4 | import hs.mediasystem.api.discovery.Discovery; 5 | import hs.mediasystem.db.core.domain.StreamTags; 6 | import hs.mediasystem.util.domain.URIs; 7 | 8 | import java.net.URI; 9 | import java.util.List; 10 | import java.util.Optional; 11 | 12 | record DiscoverEvent(URI base, Optional identificationProvider, StreamTags tags, Optional parentLocation, List discoveries) { 13 | public DiscoverEvent { 14 | if(base == null) { 15 | throw new IllegalArgumentException("base cannot be null"); 16 | } 17 | if(identificationProvider == null) { 18 | throw new IllegalArgumentException("identificationProvider cannot be null"); 19 | } 20 | if(tags == null) { 21 | throw new IllegalArgumentException("tags cannot be null"); 22 | } 23 | if(parentLocation == null) { 24 | throw new IllegalArgumentException("parentLocation cannot be null"); 25 | } 26 | if(discoveries == null) { 27 | throw new IllegalArgumentException("discoveries cannot be null"); 28 | } 29 | 30 | base = URIs.normalizeAsFolder(base); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/core/ImportSource.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.core; 2 | 3 | import hs.mediasystem.api.discovery.Discoverer; 4 | import hs.mediasystem.db.core.domain.StreamTags; 5 | 6 | import java.net.URI; 7 | import java.util.Objects; 8 | import java.util.Optional; 9 | 10 | /** 11 | * Represents a source of media to be imported. This includes how this source 12 | * should be discovered, and after discovery, how each items should be tagged and 13 | * (optionally) identified. 14 | * 15 | * @param discoverer a {@link Discoverer}, cannot be {@code null} 16 | * @param root a {@link URI}, cannot be {@code null} 17 | * @param identificationService an optional name of an identification service to use, cannot be {@code null} 18 | * @param tags a {@link StreamTags}, cannot be {@code null} 19 | */ 20 | public record ImportSource(Discoverer discoverer, URI root, Optional identificationService, StreamTags tags) { 21 | 22 | public ImportSource { 23 | Objects.requireNonNull(discoverer, "discoverer"); 24 | Objects.requireNonNull(root, "root").resolve(""); 25 | Objects.requireNonNull(tags, "tags"); 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return "ImportSource[" + discoverer.getClass().getSimpleName() + " @ " + root + " [" + tags + "]]"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/core/ResourceEvent.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.core; 2 | 3 | import hs.mediasystem.db.core.domain.Resource; 4 | 5 | import java.net.URI; 6 | 7 | interface ResourceEvent { 8 | record Updated(Resource resource) implements ResourceEvent {} 9 | record Removed(URI location) implements ResourceEvent {} 10 | } 11 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/core/StreamableEvent.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.core; 2 | 3 | import hs.mediasystem.api.datasource.services.IdentificationProvider; 4 | import hs.mediasystem.api.discovery.Discovery; 5 | import hs.mediasystem.db.core.domain.Streamable; 6 | 7 | import java.net.URI; 8 | import java.util.Optional; 9 | 10 | sealed interface StreamableEvent { 11 | 12 | URI location(); 13 | 14 | public record Updated(Streamable streamable, Optional identificationProvider, Discovery discovery) implements StreamableEvent { 15 | @Override 16 | public URI location() { 17 | return streamable.location(); 18 | } 19 | } 20 | 21 | public record Removed(URI location) implements StreamableEvent {} 22 | } 23 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/core/domain/LinkedWork.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.core.domain; 2 | 3 | import hs.mediasystem.api.datasource.WorkDescriptor; 4 | import hs.mediasystem.domain.work.WorkId; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Represents a {@link WorkDescriptor} including any resources that match it. 10 | * 11 | * @param workDescriptor a {@link WorkDescriptor}, cannot be {@code null} 12 | * @param resources a list of {@link Resource}s, cannot be {@code null}, contain {@code null}s or be empty 13 | */ 14 | public record LinkedWork(WorkDescriptor workDescriptor, List resources) { 15 | 16 | public LinkedWork { 17 | if(workDescriptor == null) { 18 | throw new IllegalArgumentException("workDescriptor cannot be null"); 19 | } 20 | if(resources == null || resources.isEmpty()) { 21 | throw new IllegalArgumentException("resources cannot be null or empty: " + resources); 22 | } 23 | } 24 | 25 | public WorkId id() { 26 | return workDescriptor.id(); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/core/domain/Resource.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.core.domain; 2 | 3 | import hs.mediasystem.api.datasource.domain.Release; 4 | import hs.mediasystem.domain.stream.ContentID; 5 | import hs.mediasystem.domain.work.Match; 6 | 7 | import java.net.URI; 8 | import java.util.List; 9 | 10 | public record Resource(Streamable streamable, Match match, List releases) { 11 | 12 | public Resource { 13 | if(streamable == null) { 14 | throw new IllegalArgumentException("streamable cannot be null"); 15 | } 16 | if(match == null) { 17 | throw new IllegalArgumentException("match cannot be null"); 18 | } 19 | if(releases == null || releases.isEmpty()) { 20 | throw new IllegalArgumentException("releases cannot be null or empty: " + releases); 21 | } 22 | } 23 | 24 | public URI location() { 25 | return streamable.location(); 26 | } 27 | 28 | public ContentID contentId() { 29 | return streamable.contentId(); 30 | } 31 | } -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/core/domain/StreamTags.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.core.domain; 2 | 3 | import java.util.Collections; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | public record StreamTags(Set tags) { 8 | 9 | public StreamTags { 10 | tags = Collections.unmodifiableSet(new HashSet<>(tags)); 11 | } 12 | 13 | public boolean contains(String tag) { 14 | return tags.contains(tag); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/events/Serializer.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.events; 2 | 3 | /** 4 | * An interface which converts objects to and from byte arrays. 5 | * 6 | * @param the object type 7 | */ 8 | public interface Serializer { 9 | 10 | /** 11 | * Converts the given value to a byte array. 12 | * 13 | * @param value a value, cannot be {@code null} 14 | * @return a byte array, never {@code null} 15 | * @throws NullPointerException when {@code value} is {@code null} 16 | * @throws SerializerException when serialization failed 17 | */ 18 | byte[] serialize(T value) throws SerializerException; 19 | 20 | /** 21 | * Converts the given serialized data to an object of type {@code T}. 22 | * 23 | * @param serialized a byte array produced by {@link Serializer#serialize(Object)}, cannot be {@code null} 24 | * @return an instance of type {@code T}, never {@code null} 25 | * @throws NullPointerException when {@code serialized} is {@code null} 26 | * @throws SerializerException when deserialization failed 27 | */ 28 | T unserialize(byte[] serialized) throws SerializerException; 29 | } 30 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/events/SerializerException.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.events; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Thrown to indicate a problem during serialization or deserialization. 7 | */ 8 | public class SerializerException extends RuntimeException { 9 | 10 | /** 11 | * Constructs a new instance. 12 | * 13 | * @param message a message, cannot be {@code null} 14 | * @param cause a cause, can be {@code null} 15 | * @throws NullPointerException when {@code message} is {@code null} 16 | */ 17 | public SerializerException(String message, Throwable cause) { 18 | super(Objects.requireNonNull(message, "message"), cause); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/services/ImageService.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.services; 2 | 3 | import hs.mediasystem.db.extract.StreamDescriptorStore; 4 | import hs.mediasystem.domain.stream.ContentID; 5 | 6 | import java.io.IOException; 7 | import java.sql.SQLException; 8 | import java.util.Optional; 9 | 10 | import javax.inject.Inject; 11 | import javax.inject.Singleton; 12 | 13 | @Singleton 14 | public class ImageService { 15 | @Inject private StreamDescriptorStore store; 16 | 17 | public Optional findImage(String id) throws IOException { 18 | String[] parts = id.split(":"); 19 | 20 | try { 21 | return Optional.ofNullable(store.readSnapshot(new ContentID(Integer.parseInt(parts[0])), Integer.parseInt(parts[1]))); 22 | } 23 | catch(SQLException e) { 24 | throw new IOException("Unable to read snapshot from database: " + id, e); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/java/hs/mediasystem/db/uris/UriRecord.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.uris; 2 | 3 | public record UriRecord(Integer id, int contentId, String uri) { 4 | 5 | public UriRecord with(int contentId) { 6 | return new UriRecord(id, contentId, uri); 7 | } 8 | 9 | } 10 | 11 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.1.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE dbinfo ( 2 | name varchar(50) NOT NULL, 3 | value varchar(50) NOT NULL, 4 | 5 | CONSTRAINT dbinfo_pk PRIMARY KEY (name) 6 | ); 7 | 8 | INSERT INTO dbinfo (name, value) VALUES ('version', '0'); 9 | 10 | CREATE TABLE localmedia ( 11 | id varchar(300) NOT NULL, 12 | scannerid bigint NOT NULL, 13 | deletetime timestamp, 14 | json ${BinaryType} NOT NULL, 15 | 16 | CONSTRAINT localmedia_id PRIMARY KEY (id) 17 | ); -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.11.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE stream_metadata ( 2 | stream_id int4 NOT NULL REFERENCES stream_ids(id) ON DELETE CASCADE, 3 | modtime bigint NOT NULL, 4 | version int4 NOT NULL, 5 | json ${BinaryType} NOT NULL, 6 | 7 | CONSTRAINT stream_metadata_pk PRIMARY KEY (stream_id) 8 | ); 9 | 10 | CREATE TABLE stream_metadata_snapshots ( 11 | stream_id int4 NOT NULL REFERENCES stream_ids(id) ON DELETE CASCADE, 12 | index int4 NOT NULL, 13 | image ${BinaryType} NOT NULL, 14 | 15 | CONSTRAINT stream_metadata_snapshots_pk PRIMARY KEY (stream_id, index) 16 | ); 17 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.12.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE stream_ids ADD COLUMN lastseentime bigint; -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.13.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE streams ( 2 | stream_id int4 NOT NULL, 3 | scanner_id int4 NOT NULL, 4 | lastenrichtime int8, 5 | nextenrichtime int8, 6 | json ${BinaryType} NOT NULL, 7 | 8 | CONSTRAINT streams_pk PRIMARY KEY (stream_id) 9 | ); 10 | 11 | CREATE TABLE descriptors ( 12 | identifier varchar(1000) NOT NULL, 13 | lastusedtime int8 NOT NULL, 14 | json ${BinaryType} NOT NULL, 15 | 16 | CONSTRAINT descriptors_pk PRIMARY KEY (identifier) 17 | ); 18 | 19 | DROP TABLE localmedia; -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.14.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE streams ADD COLUMN creation_ms int8 NOT NULL DEFAULT extract(epoch from now()); 2 | 3 | UPDATE streams SET creation_ms = sids.modtime FROM (SELECT id, modtime FROM stream_ids) AS sids WHERE sids.id = stream_id; 4 | 5 | ALTER TABLE streams ALTER COLUMN creation_ms DROP DEFAULT; -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.15.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE stream_identifier ( 2 | stream_id int4 NOT NULL REFERENCES streams(stream_id) ON DELETE CASCADE, 3 | identifier varchar(1000) NOT NULL, 4 | 5 | CONSTRAINT stream_identifier_pk PRIMARY KEY (stream_id, identifier) 6 | ); 7 | 8 | ALTER TABLE streams ADD CONSTRAINT streams_stream_ids_fk FOREIGN KEY (stream_id) REFERENCES stream_ids(id) ON DELETE CASCADE; 9 | ALTER TABLE streams ADD COLUMN parent_stream_id int4 REFERENCES streams(stream_id) ON DELETE CASCADE; 10 | ALTER TABLE streams ADD COLUMN match_type varchar(100); 11 | ALTER TABLE streams ADD COLUMN match_ms int8; 12 | ALTER TABLE streams ADD COLUMN match_accuracy float4; 13 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.16.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE stream_ids RENAME TO content_prints; 2 | 3 | ALTER TABLE uris RENAME COLUMN stream_id TO content_id; 4 | 5 | ALTER TABLE stream_metadata RENAME COLUMN stream_id TO content_id; 6 | 7 | ALTER TABLE stream_metadata_snapshots RENAME COLUMN stream_id TO content_id; 8 | 9 | ALTER TABLE streams RENAME COLUMN stream_id TO content_id; 10 | ALTER TABLE streams RENAME COLUMN parent_stream_id TO parent_content_id; 11 | 12 | ALTER TABLE stream_identifier RENAME COLUMN stream_id TO content_id; 13 | 14 | ALTER TABLE streamstate RENAME COLUMN stream_id TO content_id; -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.17.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX content_prints_lastseentime_idx ON content_prints (lastseentime); 2 | 3 | DROP TABLE stream_identifier; 4 | DROP TABLE streams; 5 | 6 | CREATE TABLE streams ( 7 | id serial4 NOT NULL PRIMARY KEY, 8 | parent_id int4 REFERENCES streams(id) ON DELETE CASCADE, 9 | 10 | content_id int4 NOT NULL REFERENCES content_prints(id) ON DELETE CASCADE, 11 | scanner_id int4 NOT NULL, 12 | name varchar(1000) NOT NULL, 13 | lastenrichtime int8, 14 | nextenrichtime int8, 15 | json ${BinaryType} NOT NULL, 16 | creation_ms int8 NOT NULL, 17 | 18 | match_type varchar(100), 19 | match_ms int8, 20 | match_accuracy float4, 21 | 22 | CONSTRAINT streams_unique UNIQUE (content_id, scanner_id, name) 23 | ); 24 | 25 | CREATE TABLE stream_identifier ( 26 | stream_id int4 NOT NULL REFERENCES streams(id) ON DELETE CASCADE, 27 | identifier varchar(1000) NOT NULL, 28 | 29 | CONSTRAINT stream_identifier_pk PRIMARY KEY (stream_id, identifier) 30 | ); 31 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.18.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE images ADD COLUMN key VARCHAR(1000); 2 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.19.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE images RENAME COLUMN key TO logical_key; 2 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.2.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE streamdata ( 2 | id ${SerialType}, 3 | url varchar(2000) NOT NULL, 4 | hash ${Sha256Type} NOT NULL, 5 | size bigint NOT NULL, 6 | modtime bigint NOT NULL, 7 | json ${BinaryType} NOT NULL, 8 | 9 | CONSTRAINT streamdata_id PRIMARY KEY (id), 10 | CONSTRAINT streamdata_url UNIQUE (url, size, modtime), 11 | CONSTRAINT streamdata_hash UNIQUE (hash, size, modtime) 12 | ); -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.3.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE settings ( 2 | id ${SerialType}, 3 | 4 | system varchar(100) NOT NULL, 5 | persistlevel varchar(20) NOT NULL, 6 | name varchar(2000) NOT NULL, 7 | value varchar(2000) NOT NULL, 8 | 9 | lastupdated timestamp NOT NULL, 10 | 11 | CONSTRAINT settings_id PRIMARY KEY (id), 12 | CONSTRAINT settings_system_key UNIQUE (system, name) 13 | ); -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.4.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE streamdata ALTER COLUMN size ${DropNotNull}; -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.5.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE images ( 2 | url varchar(1000) NOT NULL, 3 | creationtime timestamp NOT NULL, 4 | accesstime timestamp NOT NULL, 5 | image ${BinaryType} NOT NULL, 6 | 7 | CONSTRAINT images_url PRIMARY KEY (url) 8 | ); -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.6.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE streamstate ( 2 | hash ${Sha256Type} NOT NULL, 3 | size bigint, 4 | modtime bigint NOT NULL, 5 | 6 | json ${BinaryType} NOT NULL, 7 | 8 | CONSTRAINT streamstate_unique PRIMARY KEY (hash, size, modtime), 9 | FOREIGN KEY (hash, size, modtime) REFERENCES streamdata(hash, size, modtime) ON DELETE CASCADE 10 | ); -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.7.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE streamdata DROP CONSTRAINT streamdata_url; 2 | ALTER TABLE streamdata ADD CONSTRAINT streamdata_url UNIQUE (url); 3 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v0.9.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE streamdata DROP COLUMN json; 2 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v1.1.sql: -------------------------------------------------------------------------------- 1 | 2 | # Add new column: 3 | ALTER TABLE content_prints ADD COLUMN creation_ms bigint; 4 | 5 | # By default, set to time of content: 6 | UPDATE content_prints SET creation_ms = modtime; 7 | 8 | # Update as many as possible from streams: 9 | UPDATE content_prints cp SET creation_ms = s.creation_ms FROM (SELECT * FROM streams) AS s WHERE cp.id = s.content_id; 10 | 11 | # Put on the NOT NULL constraint: 12 | ALTER TABLE content_prints ALTER COLUMN creation_ms SET NOT NULL; -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v1.2.sql: -------------------------------------------------------------------------------- 1 | 2 | # Convert Duration of 0 to null in metadata: 3 | UPDATE stream_metadata SET json = (convert_from(json, 'UTF8')::jsonb || jsonb '{"duration": null}')::text::bytea 4 | WHERE convert_from(json, 'UTF8')::json ->> 'duration' = 'PT0S'; 5 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v1.3.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE identifications ( 2 | location varchar NOT NULL, 3 | identification ${BinaryType} NOT NULL, 4 | create_time timestamp with time zone NOT NULL, 5 | update_time timestamp with time zone NOT NULL, 6 | 7 | CONSTRAINT identifications_pk PRIMARY KEY (location) 8 | ); 9 | -------------------------------------------------------------------------------- /mediasystem-db/src/main/resources/db-scripts/db-v1.4.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE stream_descriptors ( 2 | content_id integer NOT NULL, 3 | descriptor ${BinaryType} NOT NULL, 4 | create_time timestamp with time zone NOT NULL, 5 | update_time timestamp with time zone NOT NULL, 6 | 7 | CONSTRAINT stream_descriptors_pk PRIMARY KEY (content_id), 8 | CONSTRAINT stream_descriptors_stream_id_fkey FOREIGN KEY (content_id) 9 | REFERENCES content_prints (id) ON DELETE CASCADE 10 | ); 11 | 12 | CREATE TABLE stream_descriptor_snapshots ( 13 | content_id integer NOT NULL, 14 | index integer NOT NULL, 15 | image ${BinaryType} NOT NULL, 16 | 17 | CONSTRAINT stream_descriptor_snapshots_pk PRIMARY KEY (content_id, index), 18 | CONSTRAINT stream_descriptor_snapshots_stream_id_fkey FOREIGN KEY (content_id) 19 | REFERENCES content_prints (id) ON DELETE CASCADE 20 | ); 21 | 22 | DROP TABLE stream_metadata; 23 | DROP TABLE stream_metadata_snapshots; -------------------------------------------------------------------------------- /mediasystem-db/src/test/java/hs/mediasystem/db/ArchitectureTest.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db; 2 | 3 | import com.tngtech.archunit.junit.AnalyzeClasses; 4 | import com.tngtech.archunit.junit.ArchTest; 5 | import com.tngtech.archunit.lang.ArchRule; 6 | import com.tngtech.archunit.library.DependencyRules; 7 | 8 | import java.lang.invoke.MethodHandles; 9 | 10 | import org.junit.jupiter.api.Test; 11 | 12 | import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | @AnalyzeClasses(packages = ArchitectureTest.BASE_PACKAGE_NAME) 16 | public class ArchitectureTest { 17 | static final String BASE_PACKAGE_NAME = "hs.mediasystem.db"; 18 | 19 | @ArchTest 20 | private final ArchRule packagesShouldBeFreeOfCycles = slices().matching("(**)").should().beFreeOfCycles(); 21 | 22 | @ArchTest 23 | private final ArchRule noClassesShouldDependOnUpperPackages = DependencyRules.NO_CLASSES_SHOULD_DEPEND_UPPER_PACKAGES; 24 | 25 | @Test 26 | void shouldMatchPackageName() { 27 | assertThat(BASE_PACKAGE_NAME).isEqualTo(MethodHandles.lookup().lookupClass().getPackageName()); 28 | } 29 | } -------------------------------------------------------------------------------- /mediasystem-db/src/test/java/hs/mediasystem/db/util/Productions.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.db.util; 2 | 3 | import hs.mediasystem.api.datasource.domain.Classification; 4 | import hs.mediasystem.api.datasource.domain.Details; 5 | import hs.mediasystem.api.datasource.domain.Production; 6 | import hs.mediasystem.domain.stream.MediaType; 7 | import hs.mediasystem.domain.work.DataSource; 8 | import hs.mediasystem.domain.work.Reception; 9 | import hs.mediasystem.domain.work.WorkId; 10 | import hs.mediasystem.util.image.ImageURI; 11 | 12 | import java.time.LocalDate; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | public class Productions { 18 | 19 | public static Production create() { 20 | return new Production( 21 | new WorkId(DataSource.instance("TMDB"), MediaType.MOVIE, "12345"), 22 | new Details("The Terminator", "Subtitle", "Robot kills humans", LocalDate.of(1984, 6, 6), new ImageURI("http://localhost", "key"), null, new ImageURI("http://localhost", "key")), 23 | new Reception(8, 12345), 24 | null, 25 | "Skynet Comes", 26 | new Classification( 27 | Arrays.asList("Action", "Science-Fiction"), 28 | Arrays.asList("en"), 29 | List.of(), 30 | Map.of(), 31 | false 32 | ), 33 | 20.0 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mediasystem-db/src/test/resources/testdata/mediasystem-collections.yaml: -------------------------------------------------------------------------------- 1 | - title: "Movies" 2 | type: "Movie" 3 | tag: "movie" 4 | 5 | - title: "TV Shows" 6 | type: "Serie" 7 | tag: "serie" 8 | -------------------------------------------------------------------------------- /mediasystem-db/src/test/resources/testdata/mediasystem-imports.yaml: -------------------------------------------------------------------------------- 1 | - type: "Movies" 2 | paths: ["testdata/movies"] 3 | identification: "TMDB" 4 | tags: ["movie"] 5 | id: 1 6 | 7 | - type: "Series" 8 | paths: ["testdata/series"] 9 | identification: "TMDB" 10 | tags: ["serie"] 11 | id: 2 12 | -------------------------------------------------------------------------------- /mediasystem-db/src/test/resources/testdata/movies/Avatar.txt: -------------------------------------------------------------------------------- 1 | Some data -------------------------------------------------------------------------------- /mediasystem-db/src/test/resources/testdata/movies/Matrix.txt: -------------------------------------------------------------------------------- 1 | Some data -------------------------------------------------------------------------------- /mediasystem-db/src/test/resources/testdata/movies/Terminator.txt: -------------------------------------------------------------------------------- 1 | Some data -------------------------------------------------------------------------------- /mediasystem-db/src/test/resources/testdata/series/Friends/friends_1x01.txt: -------------------------------------------------------------------------------- 1 | some data -------------------------------------------------------------------------------- /mediasystem-db/src/test/resources/testdata/series/Friends/friends_1x02.txt: -------------------------------------------------------------------------------- 1 | some data -------------------------------------------------------------------------------- /mediasystem-domain/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /mediasystem-domain/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | hs.mediasystem 8 | mediasystem-parent 9 | ${revision} 10 | 11 | 12 | mediasystem-domain 13 | 14 | 15 | 16 | hs.mediasystem 17 | mediasystem-util 18 | ${revision} 19 | 20 | 21 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/media/AudioTrack.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.media; 2 | 3 | public record AudioTrack(String title, String language, String codec, long channelLayout) { 4 | } 5 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/media/MediaStructure.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.media; 2 | 3 | import java.util.List; 4 | 5 | public record MediaStructure(List videoTracks, List audioTracks, List subtitleTracks) { 6 | public MediaStructure { 7 | if(videoTracks == null) { 8 | throw new IllegalArgumentException("videoTracks cannot be null"); 9 | } 10 | if(audioTracks == null) { 11 | throw new IllegalArgumentException("audioTracks cannot be null"); 12 | } 13 | if(subtitleTracks == null) { 14 | throw new IllegalArgumentException("subtitleTracks cannot be null"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/media/Resolution.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.media; 2 | 3 | public record Resolution(int width, int height, Float pixelAspectRatio) implements Comparable { 4 | 5 | @Override 6 | public int compareTo(Resolution o) { 7 | int c = Long.signum((long)width * height - (long)o.width * o.height); 8 | 9 | if(c != 0) { 10 | return c; 11 | } 12 | 13 | if(pixelAspectRatio == null) { 14 | return o.pixelAspectRatio == null ? 0 : -1; 15 | } 16 | 17 | return Float.compare(pixelAspectRatio, o.pixelAspectRatio); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/media/Snapshot.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.media; 2 | 3 | import hs.mediasystem.util.image.ImageURI; 4 | 5 | public record Snapshot(ImageURI imageUri, int frameNumber) { 6 | } 7 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/media/StreamDescriptor.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.media; 2 | 3 | import java.time.Duration; 4 | import java.util.List; 5 | import java.util.Objects; 6 | import java.util.Optional; 7 | 8 | public record StreamDescriptor( 9 | Optional duration, 10 | List videoTracks, 11 | List audioTracks, 12 | List subtitleTracks, 13 | List snapshots 14 | ) { 15 | public StreamDescriptor { 16 | if(duration == null) { 17 | throw new IllegalArgumentException("duration cannot be null"); 18 | } 19 | if(videoTracks == null) { 20 | throw new IllegalArgumentException("videoTracks cannot be null"); 21 | } 22 | if(audioTracks == null) { 23 | throw new IllegalArgumentException("audioTracks cannot be null"); 24 | } 25 | if(subtitleTracks == null) { 26 | throw new IllegalArgumentException("subtitleTracks cannot be null"); 27 | } 28 | if(snapshots == null) { 29 | throw new IllegalArgumentException("snapshots cannot be null"); 30 | } 31 | if(snapshots.stream().filter(Objects::isNull).findAny().isPresent()) { 32 | throw new IllegalArgumentException("snapshots cannot contain nulls"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/media/SubtitleTrack.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.media; 2 | 3 | public record SubtitleTrack(String title, String language, String codec) { 4 | } 5 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/media/VideoTrack.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.media; 2 | 3 | public record VideoTrack(String title, String language, String codec, Resolution resolution, Long frameCount, Float frameRate) { 4 | } 5 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/stream/ContentID.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.stream; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Unique identifier for a stream based on its content. The same streams 7 | * will have the same identifier, even if they have different names. 8 | */ 9 | public class ContentID { 10 | private final int id; 11 | 12 | public ContentID(int id) { 13 | this.id = id; 14 | } 15 | 16 | public int asInt() { 17 | return id; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return "ContentID(" + id + ")"; 23 | } 24 | 25 | @Override 26 | public int hashCode() { 27 | return Objects.hash(id); 28 | } 29 | 30 | @Override 31 | public boolean equals(Object obj) { 32 | if(this == obj) { 33 | return true; 34 | } 35 | if(obj == null || getClass() != obj.getClass()) { 36 | return false; 37 | } 38 | 39 | ContentID other = (ContentID)obj; 40 | 41 | return id == other.id; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/work/Collection.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.work; 2 | 3 | import hs.mediasystem.util.image.ImageURI; 4 | 5 | import java.util.Optional; 6 | 7 | // TODO Not to be confused with TMDB collections; perhaps rename to library 8 | public record Collection(String title, Optional cover, Optional backdrop, CollectionDefinition definition) { 9 | } 10 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/work/CollectionDefinition.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.work; 2 | 3 | public record CollectionDefinition(String type, String tag, String title) { 4 | } -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/work/CollectionId.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.work; 2 | 3 | public class CollectionId extends AbstractId { 4 | public CollectionId(DataSource dataSource, String key) { 5 | super(dataSource, key); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/work/Context.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.work; 2 | 3 | import hs.mediasystem.domain.stream.MediaType; 4 | import hs.mediasystem.util.image.ImageURI; 5 | 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | 9 | public record Context(WorkId id, String title, Optional cover, Optional backdrop) { 10 | public Context { 11 | Objects.requireNonNull(id, "id"); 12 | Objects.requireNonNull(cover, "cover"); 13 | Objects.requireNonNull(backdrop, "backdrop"); 14 | 15 | if(Objects.requireNonNull(title, "title").isBlank()) { 16 | throw new IllegalArgumentException("title cannot be blank"); 17 | } 18 | } 19 | 20 | public MediaType getType() { 21 | return id.getType(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/work/DataSource.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.work; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class DataSource { 7 | private static final Map knownDataSources = new HashMap<>(); 8 | 9 | private final String name; 10 | 11 | public static synchronized DataSource instance(String name) { // synchronized as this can be called by multiple threads 12 | return knownDataSources.computeIfAbsent(name, k -> new DataSource(name)); 13 | } 14 | 15 | private DataSource(String name) { 16 | if(name == null || name.isBlank()) { 17 | throw new IllegalArgumentException("name cannot be null or blank: " + name); 18 | } 19 | 20 | this.name = name; 21 | } 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return name; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/work/KeywordId.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.work; 2 | 3 | public class KeywordId extends AbstractId { 4 | public KeywordId(DataSource dataSource, String key) { 5 | super(dataSource, key); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/work/PersonId.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.work; 2 | 3 | public class PersonId extends AbstractId { 4 | public PersonId(DataSource dataSource, String key) { 5 | super(dataSource, key); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/work/Reception.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.work; 2 | 3 | import java.util.Comparator; 4 | 5 | public record Reception(double rating, long voteCount) { 6 | public static final Comparator RATING_REVERSED = Comparator.comparingDouble(Reception::rating).reversed(); 7 | public static final Reception EMPTY = new Reception(0, 0); 8 | } 9 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/work/RoleId.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.work; 2 | 3 | public class RoleId extends AbstractId { 4 | public RoleId(DataSource dataSource, String key) { 5 | super(dataSource, key); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/work/State.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.work; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.Optional; 6 | 7 | /** 8 | * Represents the state of a Streamable. The watched state is independent 9 | * of the resume position (a watched worked can be watched again). 10 | * 11 | * @param lastConsumptionTime the last consumption time 12 | * @param consumed true if consumed, otherwise false 13 | * @param resumePosition the resume position, never {@code null} and never negative 14 | */ 15 | public record State(Optional lastConsumptionTime, boolean consumed, Duration resumePosition) { 16 | public State { 17 | if(resumePosition == null || resumePosition.isNegative()) { 18 | throw new IllegalArgumentException("resumePosition cannot be null or negative: " + resumePosition); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mediasystem-domain/src/main/java/hs/mediasystem/domain/work/VideoLink.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.domain.work; 2 | 3 | /** 4 | * Represents a link to a video. 5 | * 6 | * @param type the {@link Type}, can be {@code null} if unknown 7 | * @param name a name, cannot be {@code null} 8 | * @param site a site, cannot be {@code null} 9 | * @param key a key, cannot be {@code null} 10 | * @param size the size, always positive 11 | */ 12 | public record VideoLink(Type type, String name, String site, String key, int size) { 13 | public enum Type {TRAILER, CLIP, TEASER, FEATURETTE} 14 | 15 | public VideoLink { 16 | if(name == null) { 17 | throw new IllegalArgumentException("name cannot be null"); 18 | } 19 | if(site == null) { 20 | throw new IllegalArgumentException("site cannot be null"); 21 | } 22 | if(key == null) { 23 | throw new IllegalArgumentException("key cannot be null"); 24 | } 25 | if(size <= 0) { 26 | throw new IllegalArgumentException("size must be positive"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mediasystem-ext-local/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /mediasystem-ext-local/src/main/java/hs/mediasystem/ext/local/Description.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.local; 2 | 3 | import java.time.LocalDate; 4 | import java.util.List; 5 | 6 | public class Description { 7 | private final String title; 8 | private final String subtitle; 9 | private final String description; 10 | private final String tagLine; 11 | private final List genres; 12 | private final LocalDate date; 13 | 14 | public Description(String title, String subtitle, String description, String tagLine, List genres, LocalDate date) { 15 | this.title = title; 16 | this.subtitle = subtitle; 17 | this.description = description; 18 | this.tagLine = tagLine; 19 | this.genres = genres; 20 | this.date = date; 21 | } 22 | 23 | public String getTitle() { 24 | return title; 25 | } 26 | 27 | public String getSubtitle() { 28 | return subtitle; 29 | } 30 | 31 | public String getDescription() { 32 | return description; 33 | } 34 | 35 | public String getTagLine() { 36 | return tagLine; 37 | } 38 | 39 | public List getGenres() { 40 | return genres; 41 | } 42 | 43 | public LocalDate getDate() { 44 | return date; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /mediasystem-ext-mpv/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /dependency-reduced-pom.xml 3 | -------------------------------------------------------------------------------- /mediasystem-ext-mpv/src/main/java/hs/mediasystem/ext/mpv/NativeWindowMPVPlayerFactory.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.mpv; 2 | 3 | import hs.mediasystem.ui.api.player.PlayerFactory; 4 | import hs.mediasystem.ui.api.player.PlayerPresentation; 5 | import hs.mediasystem.ui.api.player.PlayerWindowIdSupplier; 6 | 7 | import javax.inject.Inject; 8 | import javax.inject.Singleton; 9 | 10 | @Singleton 11 | public class NativeWindowMPVPlayerFactory implements PlayerFactory { 12 | @Inject private PlayerWindowIdSupplier supplier; 13 | 14 | @Override 15 | public String getName() { 16 | return "MPV"; 17 | } 18 | 19 | @Override 20 | public PlayerPresentation create() { 21 | return new MPVPlayer(supplier); 22 | } 23 | 24 | @Override 25 | public IntegrationMethod getIntegrationMethod() { 26 | return IntegrationMethod.WINDOW; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mediasystem-ext-mpv/src/main/resources/win32-x86-64/mpv.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjohn/MediaSystem-v2/73898d0da11f71db0b46b4a7f14e6089f426ea8e/mediasystem-ext-mpv/src/main/resources/win32-x86-64/mpv.dll -------------------------------------------------------------------------------- /mediasystem-ext-scanners/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /mediasystem-ext-scanners/src/main/java/hs/mediasystem/ext/scanners/Constants.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.scanners; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class Constants { 6 | public static final Pattern VIDEOS = Pattern.compile("(?i).+\\.(avi|flv|mkv|mov|mp4|mpg|mpeg|ogm)"); 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-ext-scanners/src/main/java/hs/mediasystem/ext/scanners/Paths.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.scanners; 2 | 3 | import java.net.URI; 4 | import java.net.URISyntaxException; 5 | import java.nio.file.Path; 6 | 7 | public class Paths { 8 | 9 | /** 10 | * Appends the relative path (which must be known to be a file) to a base URI 11 | * and returns the URI of the new path.

12 | * 13 | * This avoids the known to be slow {@link Path#toUri()} method (it does a file system 14 | * call to check if the path indicates a file or a directory). 15 | * 16 | * @param base a base {@link URI}, cannot be {@code null} 17 | * @param relative a relative {@link Path} which must be a file, cannot be {@code null} 18 | * @return a new {@link URI}, never {@code null} 19 | */ 20 | static URI appendFilePath(URI base, Path relative) { 21 | @SuppressWarnings("resource") 22 | String pathSeparator = relative.getFileSystem().getSeparator(); 23 | String relativeString = relative.toString(); 24 | 25 | if(!pathSeparator.equals("/")) { 26 | relativeString = relativeString.replace(pathSeparator, "/"); 27 | } 28 | 29 | try { 30 | return base.resolve(new URI(null, null, relativeString, null)); 31 | } 32 | catch(URISyntaxException e) { 33 | throw new IllegalStateException("Problem converting to URI: " + base + " + " + relative, e); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mediasystem-ext-scanners/src/test/resources/Movies/2012 [2009].mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjohn/MediaSystem-v2/73898d0da11f71db0b46b4a7f14e6089f426ea8e/mediasystem-ext-scanners/src/test/resources/Movies/2012 [2009].mkv -------------------------------------------------------------------------------- /mediasystem-ext-scanners/src/test/resources/Movies/A-team, The [2010, 1080p].mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjohn/MediaSystem-v2/73898d0da11f71db0b46b4a7f14e6089f426ea8e/mediasystem-ext-scanners/src/test/resources/Movies/A-team, The [2010, 1080p].mkv -------------------------------------------------------------------------------- /mediasystem-ext-scanners/src/test/resources/Movies/Alice [(1461312), Fantasy, 720p].avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjohn/MediaSystem-v2/73898d0da11f71db0b46b4a7f14e6089f426ea8e/mediasystem-ext-scanners/src/test/resources/Movies/Alice [(1461312), Fantasy, 720p].avi -------------------------------------------------------------------------------- /mediasystem-ext-scanners/src/test/resources/Movies/Underworld - 03 - Rise of the Lycans [2009, Action Fantasy Horror Thriller, 1080p] - Copy.mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjohn/MediaSystem-v2/73898d0da11f71db0b46b4a7f14e6089f426ea8e/mediasystem-ext-scanners/src/test/resources/Movies/Underworld - 03 - Rise of the Lycans [2009, Action Fantasy Horror Thriller, 1080p] - Copy.mkv -------------------------------------------------------------------------------- /mediasystem-ext-tmdb/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /mediasystem-ext-tmdb/src/main/java/hs/mediasystem/ext/tmdb/DataSources.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.tmdb; 2 | 3 | import hs.mediasystem.domain.work.DataSource; 4 | 5 | public class DataSources { 6 | public static final DataSource TMDB = DataSource.instance("TMDB"); 7 | public static final DataSource IMDB = DataSource.instance("IMDB"); 8 | } 9 | -------------------------------------------------------------------------------- /mediasystem-ext-tmdb/src/main/java/hs/mediasystem/ext/tmdb/Genres.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.tmdb; 2 | 3 | public class Genres { 4 | public static String toString(int id) { 5 | switch(id) { 6 | case 12: return "Adventure"; 7 | case 14: return "Fantasy"; 8 | case 16: return "Animation"; 9 | case 18: return "Drama"; 10 | case 27: return "Horror"; 11 | case 28: return "Action"; 12 | case 35: return "Comedy"; 13 | case 36: return "History"; 14 | case 37: return "Western"; 15 | case 53: return "Thriller"; 16 | case 80: return "Crime"; 17 | case 99: return "Documentary"; 18 | case 878: return "Science Fiction"; 19 | case 9648: return "Mystery"; 20 | case 10402: return "Music"; 21 | case 10749: return "Romance"; 22 | case 10751: return "Family"; 23 | case 10752: return "War"; 24 | case 10759: return "Action & Adventure"; 25 | case 10762: return "Kids"; 26 | case 10763: return "News"; 27 | case 10764: return "Reality"; 28 | case 10765: return "Sci-Fi & Fantasy"; 29 | case 10766: return "Soap"; 30 | case 10767: return "Talk"; 31 | case 10768: return "War & Politics"; 32 | case 10770: return "TV Movie"; 33 | default: return "Unknown Genre " + id; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mediasystem-ext-tmdb/src/main/java/hs/mediasystem/ext/tmdb/VideoLinks.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.tmdb; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | import hs.mediasystem.domain.work.VideoLink; 6 | import hs.mediasystem.domain.work.VideoLink.Type; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import javax.inject.Singleton; 12 | 13 | @Singleton 14 | public class VideoLinks { 15 | 16 | public List toVideoLinks(JsonNode videos) { 17 | List list = new ArrayList<>(); 18 | 19 | for(JsonNode result : videos.path("results")) { 20 | list.add(new VideoLink( 21 | toType(result.path("type").textValue()), 22 | result.path("name").textValue(), 23 | result.path("site").textValue(), 24 | result.path("key").textValue(), 25 | result.path("size").intValue() // 1080 26 | )); 27 | } 28 | 29 | return list; 30 | } 31 | 32 | private static Type toType(String type) { 33 | try { 34 | return Type.valueOf(type.toUpperCase()); 35 | } 36 | catch(IllegalArgumentException e) { 37 | return null; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mediasystem-ext-tmdb/src/main/java/hs/mediasystem/ext/tmdb/movie/TmdbTop100QueryService.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.tmdb.movie; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | import hs.mediasystem.api.datasource.domain.Production; 6 | import hs.mediasystem.api.datasource.services.Top100QueryService; 7 | import hs.mediasystem.ext.tmdb.ObjectFactory; 8 | import hs.mediasystem.ext.tmdb.TheMovieDatabase; 9 | 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import javax.inject.Inject; 15 | 16 | public class TmdbTop100QueryService implements Top100QueryService { 17 | @Inject private TheMovieDatabase tmdb; 18 | @Inject private ObjectFactory objectFactory; 19 | 20 | @Override 21 | public List query() throws IOException { 22 | List productions = new ArrayList<>(); 23 | 24 | for(int i = 1; i <= 10; i++) { 25 | JsonNode info = tmdb.get("3/movie/top_rated", null, List.of("page", "" + i)); // popular? 26 | 27 | for(JsonNode result : info.path("results")) { 28 | productions.add(objectFactory.toMovie(result)); 29 | } 30 | } 31 | 32 | return productions; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mediasystem-ext-tmdb/src/main/java/hs/mediasystem/ext/tmdb/provider/MediaProvider.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.tmdb.provider; 2 | 3 | import java.io.IOException; 4 | import java.util.Optional; 5 | 6 | public interface MediaProvider { 7 | 8 | /** 9 | * Given a TMDB key, provides a specific TMDB type. If the key is unknown 10 | * or not found, returns empty. If there is an IO error, throws {@link IOException}. 11 | * 12 | * @param key a TMDB key, cannot be {@code null} 13 | * @return an optional result, never {@code null} 14 | * @throws IOException when an I/O problem occurred 15 | */ 16 | Optional provide(String key) throws IOException; 17 | } 18 | -------------------------------------------------------------------------------- /mediasystem-ext-tmdb/src/main/java/hs/mediasystem/ext/tmdb/provider/MovieProvider.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.tmdb.provider; 2 | 3 | import hs.mediasystem.api.datasource.domain.Movie; 4 | import hs.mediasystem.ext.tmdb.ObjectFactory; 5 | import hs.mediasystem.ext.tmdb.TheMovieDatabase; 6 | 7 | import java.io.IOException; 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | import javax.inject.Inject; 12 | import javax.inject.Singleton; 13 | 14 | @Singleton 15 | public class MovieProvider implements MediaProvider { 16 | @Inject private TheMovieDatabase tmdb; 17 | @Inject private ObjectFactory objectFactory; 18 | 19 | @Override 20 | public Optional provide(String key) throws IOException { 21 | // keywords,alternative_titles,recommendations,similar,reviews 22 | return tmdb.query("3/movie/" + key, "text:json:tmdb:movie:" + key, List.of("append_to_response", "keywords,release_dates")) 23 | .map(objectFactory::toMovie); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mediasystem-ext-vlc/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /dependency-reduced-pom.xml 3 | -------------------------------------------------------------------------------- /mediasystem-ext-vlc/src/main/java/hs/mediasystem/ext/vlc/AbstractVLCPlayerFactory.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.vlc; 2 | 3 | import com.sun.jna.NativeLibrary; 4 | 5 | import hs.mediasystem.ext.vlc.VLCPlayer.Mode; 6 | import hs.mediasystem.ui.api.player.PlayerFactory; 7 | import hs.mediasystem.ui.api.player.PlayerPresentation; 8 | import hs.mediasystem.ui.api.player.PlayerWindowIdSupplier; 9 | 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | 13 | public abstract class AbstractVLCPlayerFactory implements PlayerFactory { 14 | private final String name; 15 | private final Mode mode; 16 | private final PlayerWindowIdSupplier supplier; 17 | 18 | public AbstractVLCPlayerFactory(String name, Mode mode, PlayerWindowIdSupplier supplier) { 19 | this.name = name; 20 | this.mode = mode; 21 | this.supplier = supplier; 22 | } 23 | 24 | @Override 25 | public PlayerPresentation create() { 26 | Path libVlcPath; 27 | 28 | if(System.getProperty("os.arch").equals("x86")) { 29 | libVlcPath = Paths.get("c:/program files (x86)/VideoLAN/VLC"); 30 | } 31 | else { 32 | libVlcPath = Paths.get("c:/program files/VideoLAN/VLC"); 33 | } 34 | 35 | NativeLibrary.addSearchPath("libvlc", libVlcPath.toString()); 36 | 37 | return new VLCPlayer(mode, supplier); 38 | } 39 | 40 | @Override 41 | public String getName() { 42 | return name; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mediasystem-ext-vlc/src/main/java/hs/mediasystem/ext/vlc/DeferredComponentIdVideoSurface.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.vlc; 2 | 3 | import uk.co.caprica.vlcj.player.base.MediaPlayer; 4 | import uk.co.caprica.vlcj.player.embedded.videosurface.VideoSurface; 5 | import uk.co.caprica.vlcj.player.embedded.videosurface.VideoSurfaceAdapter; 6 | 7 | /** 8 | * Small hack to make it possible to supply VLCJ v3 with a window id directly. When 9 | * upgraded to VLCJ v4 this can be removed. 10 | */ 11 | public abstract class DeferredComponentIdVideoSurface extends VideoSurface { 12 | 13 | /** 14 | * Create a new video surface. 15 | * 16 | * @param videoSurfaceAdapter adapter to attach a video surface to a native media player 17 | */ 18 | public DeferredComponentIdVideoSurface(VideoSurfaceAdapter videoSurfaceAdapter) { 19 | super(videoSurfaceAdapter); 20 | } 21 | 22 | @Override 23 | public void attach(MediaPlayer mediaPlayer) { 24 | videoSurfaceAdapter.attach(mediaPlayer, getComponentId()); 25 | } 26 | 27 | /** 28 | * Get the native component id to use for the video surface. 29 | * 30 | * @return component id 31 | */ 32 | protected abstract long getComponentId(); 33 | 34 | } -------------------------------------------------------------------------------- /mediasystem-ext-vlc/src/main/java/hs/mediasystem/ext/vlc/FXCanvasVLCPlayerFactory.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.vlc; 2 | 3 | import hs.mediasystem.ext.vlc.VLCPlayer.Mode; 4 | 5 | import javax.inject.Singleton; 6 | 7 | @Singleton 8 | public class FXCanvasVLCPlayerFactory extends AbstractVLCPlayerFactory { 9 | 10 | public FXCanvasVLCPlayerFactory() { 11 | super("VLC (integrated)", Mode.CANVAS, null); 12 | } 13 | 14 | @Override 15 | public IntegrationMethod getIntegrationMethod() { 16 | return IntegrationMethod.PIXEL_BUFFER; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mediasystem-ext-vlc/src/main/java/hs/mediasystem/ext/vlc/NativeWindowVLCPlayerFactory.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.vlc; 2 | 3 | import hs.mediasystem.ext.vlc.VLCPlayer.Mode; 4 | import hs.mediasystem.ui.api.player.PlayerWindowIdSupplier; 5 | 6 | import javax.inject.Inject; 7 | import javax.inject.Singleton; 8 | 9 | @Singleton 10 | public class NativeWindowVLCPlayerFactory extends AbstractVLCPlayerFactory { 11 | 12 | @Inject 13 | public NativeWindowVLCPlayerFactory(PlayerWindowIdSupplier supplier) { 14 | super("VLC (native window)", Mode.WID, supplier); 15 | } 16 | 17 | @Override 18 | public IntegrationMethod getIntegrationMethod() { 19 | return IntegrationMethod.WINDOW; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mediasystem-ext-vlc/src/main/java/hs/mediasystem/ext/vlc/util/Accessor.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.vlc.util; 2 | 3 | public interface Accessor { 4 | T read(); 5 | void write(T value); 6 | } 7 | -------------------------------------------------------------------------------- /mediasystem-ext-vlc/src/main/java/hs/mediasystem/ext/vlc/util/BeanBooleanProperty.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ext.vlc.util; 2 | 3 | import javafx.application.Platform; 4 | import javafx.beans.property.SimpleBooleanProperty; 5 | 6 | public final class BeanBooleanProperty extends SimpleBooleanProperty { 7 | private final Accessor accessor; 8 | 9 | private boolean initialized; 10 | 11 | public BeanBooleanProperty(Object bean, String propertyName) { 12 | accessor = new BeanAccessor<>(bean, propertyName); 13 | } 14 | 15 | public BeanBooleanProperty(Accessor accessor) { 16 | this.accessor = accessor; 17 | } 18 | 19 | @Override 20 | public boolean get() { 21 | if(!initialized) { 22 | initialized = true; 23 | super.set(accessor.read()); 24 | } 25 | 26 | return super.get(); 27 | } 28 | 29 | @Override 30 | public void set(boolean value) { 31 | super.set(value); 32 | accessor.write(value); 33 | } 34 | 35 | public void update(final boolean value) { 36 | Platform.runLater(new Runnable() { 37 | @Override 38 | public void run() { 39 | BeanBooleanProperty.super.set(value); 40 | } 41 | }); 42 | } 43 | } -------------------------------------------------------------------------------- /mediasystem-jfx/.gitignore: -------------------------------------------------------------------------------- 1 | /dependency-reduced-pom.xml 2 | -------------------------------------------------------------------------------- /mediasystem-jfx/src/main/java/com/sun/javafx/binding/FilteredBinding.java: -------------------------------------------------------------------------------- 1 | package com.sun.javafx.binding; 2 | 3 | import java.util.Objects; 4 | import java.util.function.Predicate; 5 | 6 | import javafx.beans.value.ObservableValue; 7 | import javafx.util.Subscription; 8 | 9 | public class FilteredBinding extends LazyObjectBinding { 10 | 11 | private final ObservableValue source; 12 | private final Predicate predicate; 13 | 14 | public FilteredBinding(ObservableValue source, Predicate predicate) { 15 | this.source = Objects.requireNonNull(source); 16 | this.predicate = Objects.requireNonNull(predicate); 17 | } 18 | 19 | @Override 20 | protected Subscription observeSources() { 21 | return source.subscribe(this::invalidate); // start observing source 22 | } 23 | 24 | @Override 25 | protected T computeValue() { 26 | T value = source.getValue(); 27 | 28 | return value == null ? null 29 | : predicate.test(value) ? value 30 | : null; 31 | } 32 | } -------------------------------------------------------------------------------- /mediasystem-jfx/src/test/java/javafx/beans/value/ReferenceAsserts.java: -------------------------------------------------------------------------------- 1 | package javafx.beans.value; 2 | 3 | import java.lang.ref.WeakReference; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | import static org.junit.jupiter.api.Assertions.assertNull; 7 | 8 | public class ReferenceAsserts { 9 | 10 | @SuppressWarnings("all") 11 | public static void testIfStronglyReferenced(Object obj, Runnable clearRefs) { 12 | WeakReference ref = new WeakReference<>(obj); 13 | 14 | clearRefs.run(); 15 | obj = null; 16 | 17 | System.gc(); 18 | 19 | assertNotNull(ref.get()); 20 | } 21 | 22 | @SuppressWarnings("all") 23 | public static void testIfNotStronglyReferenced(Object obj, Runnable clearRefs) { 24 | WeakReference ref = new WeakReference<>(obj); 25 | 26 | clearRefs.run(); 27 | obj = null; 28 | 29 | System.gc(); 30 | 31 | assertNull(ref.get()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mediasystem-local-client/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | Logs/ 3 | mediasystem.ini 4 | mediasystem-imports.yaml 5 | logging.properties 6 | /mediasystem-collections.yaml 7 | db/ 8 | derby.log 9 | /mediasystem.yaml 10 | -------------------------------------------------------------------------------- /mediasystem-local-client/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | hs.mediasystem 8 | mediasystem-parent 9 | ${revision} 10 | 11 | 12 | mediasystem-local-client 13 | 14 | 15 | 16 | hs.mediasystem 17 | mediasystem-util 18 | ${revision} 19 | 20 | 21 | hs.mediasystem 22 | mediasystem-ui-api 23 | ${revision} 24 | 25 | 26 | hs.mediasystem 27 | mediasystem-runner 28 | ${revision} 29 | 30 | 31 | hs.mediasystem 32 | mediasystem-db 33 | ${revision} 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /mediasystem-local-client/src/main/java/hs/mediasystem/local/client/NonJavaFXFrontEndRunner.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.local.client; 2 | 3 | public class NonJavaFXFrontEndRunner { 4 | public static void main(String[] args) { 5 | FrontEndRunner.main(args); // Workaround for javafx.graphics named module check when bundled as fat jar 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-local-client/src/main/java/hs/mediasystem/local/client/service/LocalCollectionClient.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.local.client.service; 2 | 3 | import hs.mediasystem.db.services.CollectionService; 4 | import hs.mediasystem.domain.work.Collection; 5 | import hs.mediasystem.ui.api.CollectionClient; 6 | 7 | import java.util.List; 8 | 9 | import javax.inject.Inject; 10 | import javax.inject.Singleton; 11 | 12 | @Singleton 13 | public class LocalCollectionClient implements CollectionClient { 14 | @Inject private CollectionService service; 15 | 16 | @Override 17 | public List findCollections() { 18 | return service.findCollections(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /mediasystem-local-client/src/main/java/hs/mediasystem/local/client/service/LocalImageClient.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.local.client.service; 2 | 3 | import hs.mediasystem.db.services.ImageService; 4 | import hs.mediasystem.ui.api.ImageClient; 5 | 6 | import java.io.IOException; 7 | import java.util.Optional; 8 | 9 | import javax.inject.Inject; 10 | import javax.inject.Singleton; 11 | 12 | @Singleton 13 | public class LocalImageClient implements ImageClient { 14 | @Inject private ImageService service; 15 | 16 | @Override 17 | public Optional findImage(String id) throws IOException { 18 | return service.findImage(id); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /mediasystem-package/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /dependency-reduced-pom.xml 3 | /.classpath 4 | /.project 5 | /.settings/ -------------------------------------------------------------------------------- /mediasystem-package/src/main/resources/scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | java -jar mediasystem.jar 4 | -------------------------------------------------------------------------------- /mediasystem-runner/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /Logs/ 3 | /mediasystem-collections.yaml 4 | /mediasystem-imports.yaml 5 | /mediasystem.ini 6 | /logging.properties 7 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/NoModule.java: -------------------------------------------------------------------------------- 1 | //module hs.mediasystem.runner { 2 | // requires javafx.controls; 3 | // requires hs.mediasystem.util; 4 | // requires javax.inject; 5 | // requires reactfx; 6 | // requires mediasystem.ext.basic.media.types; 7 | // requires ddif.core; 8 | // requires mediasystem.domain; 9 | // requires java.desktop; 10 | // requires java.sql; 11 | // requires database.core; 12 | // requires com.fasterxml.jackson.databind; 13 | // requires com.fasterxml.jackson.core; 14 | // requires mediasystem.db; 15 | // requires ddif.plugins; 16 | // requires jlessc; 17 | // requires java.annotation; 18 | // requires hs.mediasystem.mediamanager; 19 | // requires hs.mediasystem.scanner; 20 | // requires jackson.annotations; 21 | // requires com.fasterxml.jackson.module.paramnames; 22 | // requires com.fasterxml.jackson.dataformat.yaml; 23 | // requires javafx.swing; 24 | // requires javafx.graphics; 25 | // requires com.sun.jna; 26 | // requires com.sun.jna.platform; 27 | //} 28 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/basictheme/AbstractPlacer.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.plugin.basictheme; 2 | 3 | import hs.mediasystem.presentation.NodeFactory; 4 | import hs.mediasystem.presentation.ParentPresentation; 5 | import hs.mediasystem.presentation.Placer; 6 | import hs.mediasystem.presentation.Presentation; 7 | 8 | import javafx.scene.Node; 9 | 10 | import javax.inject.Inject; 11 | 12 | public abstract class AbstractPlacer

> implements Placer { 13 | @Inject private F nodeFactory; 14 | 15 | @Override 16 | public Node place(P parentPresentation, C presentation) { 17 | linkPresentations(parentPresentation, presentation); 18 | 19 | return nodeFactory.create(presentation); 20 | } 21 | 22 | protected abstract void linkPresentations(P parentPresentation, C presentation); 23 | } 24 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/basictheme/ContributionsPlacer.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.plugin.basictheme; 2 | 3 | import hs.mediasystem.plugin.library.scene.base.LibraryNodeFactory; 4 | import hs.mediasystem.plugin.library.scene.base.LibraryPresentation; 5 | import hs.mediasystem.plugin.library.scene.grid.contribution.ContributionsPresentationFactory.ContributionsPresentation; 6 | import hs.mediasystem.plugin.library.scene.grid.contribution.ContributionsSetup; 7 | import hs.mediasystem.presentation.PlacerQualifier; 8 | 9 | import javax.inject.Singleton; 10 | 11 | @Singleton 12 | @PlacerQualifier(parent = LibraryNodeFactory.class, child = ContributionsSetup.class) 13 | public class ContributionsPlacer extends AbstractPlacer { 14 | 15 | @Override 16 | protected void linkPresentations(LibraryPresentation parentPresentation, ContributionsPresentation presentation) { 17 | parentPresentation.backdrop.bind( 18 | presentation.work 19 | .map(d -> d.getDetails().getBackdrop().orElse(null)) 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/basictheme/RecommendationsPlacer.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.plugin.basictheme; 2 | 3 | import hs.mediasystem.plugin.library.scene.base.LibraryNodeFactory; 4 | import hs.mediasystem.plugin.library.scene.base.LibraryPresentation; 5 | import hs.mediasystem.plugin.library.scene.grid.recommendation.RecommendationsSetup; 6 | import hs.mediasystem.plugin.library.scene.grid.recommendation.RecommendationsPresentationFactory.RecommendationsPresentation; 7 | import hs.mediasystem.presentation.PlacerQualifier; 8 | import hs.mediasystem.ui.api.domain.Work; 9 | 10 | import javax.inject.Singleton; 11 | 12 | @Singleton 13 | @PlacerQualifier(parent = LibraryNodeFactory.class, child = RecommendationsSetup.class) 14 | public class RecommendationsPlacer extends AbstractPlacer { 15 | 16 | @Override 17 | protected void linkPresentations(LibraryPresentation parentPresentation, RecommendationsPresentation presentation) { 18 | parentPresentation.backdrop.bind(presentation.selectedItem 19 | .filter(Work.class::isInstance) 20 | .map(Work.class::cast) 21 | .map(Work::getDetails) 22 | .map(d -> d.getBackdrop().orElse(null)) 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/home/CollectionPresentationProvider.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.plugin.home; 2 | 3 | import hs.mediasystem.presentation.Presentation; 4 | import hs.mediasystem.runner.collection.CollectionType; 5 | 6 | import java.util.Set; 7 | 8 | import javax.inject.Inject; 9 | import javax.inject.Singleton; 10 | 11 | @Singleton 12 | public class CollectionPresentationProvider { 13 | @Inject private Set collectionTypes; 14 | 15 | public Presentation createPresentation(String type, String tag) { 16 | for(CollectionType collectionType : collectionTypes) { 17 | if(collectionType.getId().equalsIgnoreCase(type)) { 18 | return collectionType.createPresentation(tag); 19 | } 20 | } 21 | 22 | return null; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/home/GeneralOptionsPresentation.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.plugin.home; 2 | 3 | import hs.mediasystem.plugin.home.HomePresentation.OptionsPresentation; 4 | import hs.mediasystem.util.image.ImageHandle; 5 | import hs.mediasystem.util.image.ResourceImageHandle; 6 | 7 | import javafx.beans.property.SimpleObjectProperty; 8 | import javafx.beans.value.ObservableValue; 9 | 10 | public final class GeneralOptionsPresentation implements OptionsPresentation { 11 | 12 | @Override 13 | public ObservableValue backdropProperty() { 14 | return new SimpleObjectProperty<>(new ResourceImageHandle(HomeScreenNodeFactory.class, "options-backdrop.jpg")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/home/exit-styles.less: -------------------------------------------------------------------------------- 1 | @import "${root}/hs/mediasystem/runner/properties.less"; 2 | 3 | .content { 4 | .text { 5 | -fx-padding: 0 0 2em 0; 6 | } 7 | 8 | .buttons { 9 | -fx-alignment: center; 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/home/exit.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4a4a05996d83fe5607765a5df58439eed830f5851bc2baa8866b9d42112080f0 3 | size 46956 4 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/home/help.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a8b89ab6d8e315472a9c56127b2fb80cad746cff781d81184c9f0533b2eec292 3 | size 141129 4 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/home/options-backdrop.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5a67fc1a84bfa9850a99d59da0c8c4f4ff55cd2036218b93703b9a55e22e3e8f 3 | size 3227859 4 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/home/styles.less: -------------------------------------------------------------------------------- 1 | @import "${root}/hs/mediasystem/runner/properties.less"; 2 | @import "${root}/hs/mediasystem/plugin/cell/annotated-image-cell-factory.less"; 3 | 4 | .menu-view { 5 | -fx-alignment: bottom-center; 6 | } 7 | 8 | .backdrop-container { 9 | .clip { 10 | -fx-background-color: 11 | radial-gradient(center 200% 50%, radius 200%, transparent, transparent 96%, rgba(0, 0, 0, 0.1) 97%, rgba(0, 0, 0, 0.3) 98%, rgba(0, 0, 0, 0.6) 99%, black) 12 | ; 13 | } 14 | } 15 | 16 | .option-container { 17 | -fx-background-color: linear-gradient(to bottom, transparent 0%, transparent 75%, rgba(40, 40, 40) 75%, black); 18 | 19 | .list-cell { 20 | #cell-layout.image-overlay(); 21 | 22 | @style-p2-base: 22; 23 | @style-p3-base: 16; 24 | } 25 | } 26 | 27 | .main-menu-container { 28 | .menu-background { 29 | -fx-font-size: 32; 30 | -fx-text-fill: fade(@text-accent, 0.4); 31 | .black; 32 | } 33 | 34 | .list-cell { 35 | -fx-text-fill: @text-primary-highlight; 36 | -fx-font-size: 30; 37 | -fx-padding: 0; 38 | .black; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/base/LibraryPresentation.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.plugin.library.scene.base; 2 | 3 | import hs.mediasystem.presentation.ParentPresentation; 4 | import hs.mediasystem.util.image.ImageHandle; 5 | 6 | import javafx.beans.property.ObjectProperty; 7 | import javafx.beans.property.SimpleObjectProperty; 8 | 9 | import javax.inject.Named; 10 | 11 | @Named 12 | public class LibraryPresentation extends ParentPresentation { 13 | public final ObjectProperty backdrop = new SimpleObjectProperty<>(); 14 | } 15 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/base/curtain.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:bc5abd9b228008a9de064a012a90ffc46a49b524bd8722bd99df4a2fba3be37d 3 | size 259072 4 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/base/styles.less: -------------------------------------------------------------------------------- 1 | @import "${root}/hs/mediasystem/runner/properties.less"; 2 | 3 | .stage { 4 | -fx-border-color: rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.2); 5 | -fx-border-width: 1 0 0 0, 1 0 0 0; 6 | -fx-border-insets: 0, 1; 7 | 8 | -fx-background-color: radial-gradient(center 50% 0%, radius 100%, derive(-c-shadow-highlight, -50%), black); 9 | } 10 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/grid/FolderPresentationFactory.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.plugin.library.scene.grid; 2 | 3 | import hs.mediasystem.plugin.library.scene.grid.common.GridViewPresentationFactory; 4 | import hs.mediasystem.ui.api.domain.Work; 5 | 6 | import java.util.List; 7 | 8 | import javax.inject.Singleton; 9 | 10 | @Singleton 11 | public class FolderPresentationFactory extends GridViewPresentationFactory { 12 | 13 | public FolderPresentation create(List items, String settingPostfix, ViewOptions viewOptions, Object contextItem) { 14 | return new FolderPresentation(settingPostfix, items, viewOptions, contextItem); 15 | } 16 | 17 | public class FolderPresentation extends GridViewPresentation { 18 | public final String settingPostfix; 19 | public final ViewOptions viewOptions; 20 | 21 | public FolderPresentation(String settingPostfix, List items, ViewOptions viewOptions, Object contextItem) { 22 | super("Folder:" + settingPostfix, viewOptions, Work::getId); 23 | 24 | this.settingPostfix = settingPostfix; 25 | this.viewOptions = viewOptions; 26 | this.inputItems.set(items); // TODO add refresh code 27 | this.rootContextItem.set(contextItem); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/grid/ProductionCollectionFactory.yaml: -------------------------------------------------------------------------------- 1 | title: Collection 2 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/grid/common/GridViewPresentationFactory.yaml: -------------------------------------------------------------------------------- 1 | sort-order: 2 | alpha: Alphabetical 3 | best: Most Relevant 4 | popularity: Most Popular 5 | rating: Highest Rated 6 | release-date: Most Recently Released 7 | watched-date: Most Recently Watched 8 | filter: 9 | none: "-" 10 | cast: Only Castings 11 | crew: Only as Crew 12 | released-recently: Released in the last 5 years 13 | watched-recently: Watched in the last 2 years 14 | stateFilter: 15 | none: "-" 16 | available: In Collection 17 | unwatched: Not Watched 18 | grouping: 19 | NoGrouping: "-" 20 | CollectionGrouping: "By Collection" 21 | GenreGrouping: "By Genre" 22 | AlphabeticalGrouping: "Alphabetical" 23 | header: 24 | order: ORDER 25 | filter: FILTER 26 | stateFilter: STATE 27 | grouping: GROUPING 28 | status-message: 29 | unfiltered: Showing %d items 30 | filtered: Showing %d out of %d items 31 | 32 | action-target: 33 | sortOrder: 34 | order: 1 35 | label: Order 36 | filter: 37 | order: 2 38 | label: Filter 39 | stateFilter: 40 | order: 3 41 | label: State 42 | grouping: 43 | order: 4 44 | label: Grouping 45 | 46 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/grid/common/WorkCellPresentation.yaml: -------------------------------------------------------------------------------- 1 | section: 2 | label: Selected Item 3 | 4 | action-target: 5 | watched: 6 | order: 5 7 | label: Watched 8 | showInfo: 9 | order: 6 10 | label: Show Stream Information 11 | reidentify: 12 | order: 7 13 | label: Reidentify Media 14 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/grid/common/WorkNotFoundException.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.plugin.library.scene.grid.common; 2 | 3 | import hs.mediasystem.domain.stream.MediaType; 4 | import hs.mediasystem.domain.work.WorkId; 5 | import hs.mediasystem.util.BeanUtils; 6 | import hs.mediasystem.util.Localizable; 7 | 8 | public class WorkNotFoundException extends RuntimeException implements Localizable { 9 | private final WorkId id; 10 | 11 | public WorkNotFoundException(WorkId id) { 12 | this.id = id; 13 | } 14 | 15 | private static String toLocalizedItemName(MediaType mediaType) { 16 | return mediaType.name().toLowerCase(); 17 | } 18 | 19 | @Override 20 | public String toLocalizedString() { 21 | return "### " + BeanUtils.capitalize(toLocalizedItemName(id.getType())) + " unavailable\n" 22 | + "MediaSystem was unable to display an item because it is no longer" 23 | + "available.\n\n" 24 | + "#### Technical details\n" 25 | + "Unable to fetch item with id `" + id + "`"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/grid/common/status-bar.less: -------------------------------------------------------------------------------- 1 | @import "${root}/hs/mediasystem/runner/properties.less"; 2 | 3 | .status-bar 4 | { 5 | -fx-spacing: 12; 6 | -fx-opacity: 0.7; 7 | 8 | .label.status-bar-element 9 | { 10 | -fx-text-fill: -c-text; 11 | -fx-font-weight: bold; 12 | -fx-min-width: 14em; 13 | } 14 | 15 | .header-with-shortcut 16 | { 17 | -fx-spacing: 0.25em; 18 | -fx-alignment: center-left; 19 | } 20 | 21 | .total 22 | { 23 | -fx-font-weight: bold; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/grid/contribution/ContributionsPresentationFactory.yaml: -------------------------------------------------------------------------------- 1 | title: Cast & Crew 2 | 3 | section: 4 | label: View 5 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/grid/generic/GenericCollectionPresentationFactory.yaml: -------------------------------------------------------------------------------- 1 | section: 2 | label: View 3 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/grid/participation/ParticipationsPresentationFactory.yaml: -------------------------------------------------------------------------------- 1 | section: 2 | label: View 3 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/grid/participation/PersonNotFoundException.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.plugin.library.scene.grid.participation; 2 | 3 | import hs.mediasystem.domain.work.PersonId; 4 | import hs.mediasystem.util.Localizable; 5 | 6 | public class PersonNotFoundException extends RuntimeException implements Localizable { 7 | private final PersonId id; 8 | 9 | public PersonNotFoundException(PersonId id) { 10 | this.id = id; 11 | } 12 | 13 | @Override 14 | public String toLocalizedString() { 15 | return "### Person unavailable\n" 16 | + "MediaSystem was unable to display an item because it is no longer" 17 | + "available.\n\n" 18 | + "#### Technical details\n" 19 | + "Unable to fetch item with id `" + id + "`"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/grid/recommendation/RecommendationsPresentationFactory.yaml: -------------------------------------------------------------------------------- 1 | title: Recommended 2 | 3 | section: 4 | label: View 5 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/overview/EpisodePresentation.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.plugin.library.scene.overview; 2 | 3 | import hs.mediasystem.presentation.Presentation; 4 | import hs.mediasystem.ui.api.domain.Work; 5 | 6 | import java.util.List; 7 | 8 | import javafx.beans.property.ObjectProperty; 9 | import javafx.beans.property.ReadOnlyObjectProperty; 10 | 11 | public class EpisodePresentation implements Presentation { 12 | private final ReadOnlyObjectProperty> episodeItems; 13 | private final ObjectProperty selectedChild; 14 | 15 | public EpisodePresentation(ReadOnlyObjectProperty> episodeItems, ObjectProperty selectedChild) { 16 | this.episodeItems = episodeItems; 17 | this.selectedChild = selectedChild; 18 | } 19 | 20 | public void previous() { 21 | List episodes = episodeItems.get(); 22 | int index = episodes.indexOf(selectedChild.getValue()); 23 | 24 | if(index > 0) { 25 | selectedChild.setValue(episodes.get(index - 1)); 26 | } 27 | } 28 | 29 | public void next() { 30 | List episodes = episodeItems.get(); 31 | int index = episodes.indexOf(selectedChild.getValue()); 32 | 33 | if(index < episodes.size() - 1) { 34 | selectedChild.setValue(episodes.get(index + 1)); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/overview/EpisodePresentation.yaml: -------------------------------------------------------------------------------- 1 | action-target: 2 | next: 3 | visible: false 4 | previous: 5 | visible: false -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/overview/ProductionPresentationFactory.yaml: -------------------------------------------------------------------------------- 1 | section: 2 | label: Production Details 3 | 4 | action-target: 5 | watched: 6 | order: 1 7 | label: Watched 8 | episodeWatched: 9 | order: 2 10 | label: Episode Watched 11 | seasonWatched: 12 | order: 3 13 | label: Season Watched 14 | showInfo: 15 | order: 4 16 | label: Show File Information -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/library/scene/overview/play-dialog.less: -------------------------------------------------------------------------------- 1 | @import "${root}/hs/mediasystem/runner/properties.less"; 2 | 3 | .dialog { 4 | .button-box { 5 | -fx-spacing: 0.4em; 6 | 7 | .button { 8 | -fx-skin: 'hs.mediasystem.plugin.library.scene.overview.NavigationDialogButtonSkin'; 9 | .button-3d(); 10 | 11 | .label { 12 | -fx-text-fill: #eeeeee; 13 | .bold; 14 | } 15 | } 16 | } 17 | .stream-button { 18 | -fx-max-width: infinity; // make all buttons equal width 19 | 20 | .button-content-box { 21 | -fx-spacing: 0.6em; 22 | 23 | .description-box { 24 | -fx-max-width: 45em; 25 | 26 | .group-title { 27 | .style-p3; 28 | } 29 | 30 | .title { 31 | .style-p1; 32 | } 33 | 34 | .info { 35 | .style-p3; 36 | } 37 | } 38 | 39 | .snapshots-box { 40 | -fx-pref-height: 5em; 41 | -fx-max-height: 5em; 42 | 43 | .image-view-effect { 44 | -fx-background-color: black; // hides a bug where border does not align on high DPI displays set at 150% (3 pixel border would be 4.5 pixels, and something goes wrong) 45 | -fx-border-width: @unit * 3; 46 | -fx-border-color: black; 47 | } 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/playback/scene/PlaybackOverlayPresentation.yaml: -------------------------------------------------------------------------------- 1 | section: 2 | label: Playback Overlay 3 | 4 | action-target: 5 | overlayVisible: 6 | order: 101 7 | label: Position and Clock visible -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/plugin/playback/scene/VideoUnavailableException.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.plugin.playback.scene; 2 | 3 | import hs.mediasystem.util.Localizable; 4 | import hs.mediasystem.util.exception.Throwables; 5 | 6 | import java.nio.file.Path; 7 | 8 | public class VideoUnavailableException extends RuntimeException implements Localizable { 9 | private final Path path; 10 | 11 | public VideoUnavailableException(Throwable cause, Path path) { 12 | super(cause); 13 | 14 | this.path = path; 15 | } 16 | 17 | @Override 18 | public String toLocalizedString() { 19 | return "### Unable to play video\n" 20 | + "MediaSystem was unable to start playing the selected content because the " 21 | + "file is unavailable or cannot be accessed.\n\n" 22 | + "#### Solution\n" 23 | + "Please check the file at this location:\n\n" 24 | + "`" + path + "`\n" 25 | + "#### Technical details\n" 26 | + "```\n" 27 | + Throwables.toString(getCause()) 28 | + "```\n"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/presentation/AbstractPresentation.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.presentation; 2 | 3 | import hs.mediasystem.runner.util.debug.DebugFX; 4 | 5 | public abstract class AbstractPresentation implements Presentation { 6 | 7 | { 8 | DebugFX.addReference(this); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/presentation/Navigable.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.presentation; 2 | 3 | import javafx.event.Event; 4 | 5 | public interface Navigable { 6 | void navigateBack(Event e); 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/presentation/Navigable.yaml: -------------------------------------------------------------------------------- 1 | action-target: 2 | navigateBack: 3 | visible: false 4 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/presentation/NodeFactory.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.presentation; 2 | 3 | import javafx.scene.Node; 4 | 5 | public interface NodeFactory

{ 6 | Node create(P presentation); 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/presentation/Placer.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.presentation; 2 | 3 | import javafx.scene.Node; 4 | 5 | public interface Placer

{ 6 | Node place(P parentPresentation, C presentation); 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/presentation/PlacerQualifier.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.presentation; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import javax.inject.Qualifier; 7 | 8 | import static java.lang.annotation.ElementType.TYPE; 9 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 10 | 11 | @Retention(RUNTIME) 12 | @Target(TYPE) 13 | @Qualifier 14 | public @interface PlacerQualifier { 15 | Class> parent(); 16 | Class> child(); 17 | } 18 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/presentation/PresentationActionEvent.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.presentation; 2 | 3 | import hs.mediasystem.runner.util.action.Action; 4 | 5 | import javafx.event.EventType; 6 | 7 | public class PresentationActionEvent extends PresentationEvent { 8 | public static final EventType ANY = new EventType<>(PresentationEvent.ANY, "PRESENTATION_ACTION"); 9 | public static final EventType PROPOSED = new EventType<>(ANY, "PRESENTATION_ACTION_PROPOSED"); 10 | 11 | public static PresentationActionEvent createActionProposal(Action action) { 12 | return new PresentationActionEvent(PROPOSED, action); 13 | } 14 | 15 | private final Action action; 16 | 17 | PresentationActionEvent(EventType type, Action action) { 18 | super(type); 19 | 20 | this.action = action; 21 | } 22 | 23 | public Action getAction() { 24 | return action; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return getClass().getName() + "[source=" + getSource() + "; target=" + getTarget() + "; type=" + getEventType() + "; consumed=" + isConsumed() + "; action=" + getAction() + "]"; 30 | } 31 | } -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/Configuration.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner; 2 | 3 | import hs.mediasystem.runner.util.FXSceneManager; 4 | import hs.mediasystem.runner.util.SceneManager; 5 | import hs.mediasystem.runner.util.debug.DebugSceneFX; 6 | 7 | import javax.annotation.Nullable; 8 | import javax.inject.Inject; 9 | import javax.inject.Named; 10 | import javax.inject.Singleton; 11 | 12 | import org.int4.dirk.annotations.Produces; 13 | 14 | @Singleton 15 | public class Configuration { 16 | @Inject @Nullable @Named("general.screen") public Long screenNumber = 0L; 17 | @Inject @Nullable @Named("general.alwaysOnTop") public Boolean alwaysOnTop = false; 18 | @Inject @Nullable @Named("general.debug.dumpUnreferencedNodes") public Integer dumpUnreferencedNodes = 0; 19 | 20 | @Produces 21 | @Singleton 22 | SceneManager sceneManager() { 23 | FXSceneManager sceneManager = new FXSceneManager("MediaSystem", screenNumber.intValue(), alwaysOnTop); 24 | 25 | if(dumpUnreferencedNodes > 0) { 26 | DebugSceneFX.monitor(sceneManager.getScene(), dumpUnreferencedNodes * 1000L); 27 | } 28 | 29 | return sceneManager; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/EventRoot.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner; 2 | 3 | import javafx.event.Event; 4 | 5 | public interface EventRoot { 6 | void fire(Event event); 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/PluginBase.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner; 2 | 3 | import hs.mediasystem.runner.util.resource.ResourceManager; 4 | 5 | import javafx.scene.image.Image; 6 | 7 | public interface PluginBase { 8 | default String getText(String name) { 9 | return ResourceManager.getText(getClass(), name); 10 | } 11 | 12 | default double getDouble(String name) { 13 | return ResourceManager.getDouble(getClass(), name); 14 | } 15 | 16 | default Image getImage(String name) { 17 | return ResourceManager.getImage(getClass(), name); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/action/option-menu-dialog.less: -------------------------------------------------------------------------------- 1 | @import "${root}/hs/mediasystem/runner/properties.less"; 2 | 3 | .option-menu-dialog { 4 | .slider-container { 5 | .slider { 6 | -fx-min-width: 25em; 7 | } 8 | 9 | .slider-value { 10 | -fx-min-width: 10em; 11 | } 12 | 13 | -fx-spacing: 10px; 14 | } 15 | 16 | .spinner { 17 | -fx-min-width: 25em; 18 | } 19 | 20 | -fx-hgap: 10px; 21 | 22 | .label.section { 23 | -fx-padding: 10 0 0 0; 24 | .black; 25 | -fx-font-size: 12; 26 | .text-fill-accent; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/collection/CollectionType.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.collection; 2 | 3 | import hs.mediasystem.presentation.Presentation; 4 | 5 | public interface CollectionType { 6 | String getId(); 7 | Presentation createPresentation(String tag); 8 | } 9 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/config/ConfigurationProvider.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.config; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; 6 | 7 | import javax.inject.Inject; 8 | import javax.inject.Named; 9 | import javax.inject.Provider; 10 | 11 | public abstract class ConfigurationProvider implements Provider { 12 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() 13 | .registerModule(new ParameterNamesModule()); 14 | 15 | private final Class configClass; 16 | private final String path; 17 | private final T defaultValue; 18 | 19 | @Inject @Named("configuration") private JsonNode config; 20 | 21 | public ConfigurationProvider(Class configClass, String path, T defaultValue) { 22 | this.configClass = configClass; 23 | this.path = path; 24 | this.defaultValue = defaultValue; 25 | } 26 | 27 | @Override 28 | public T get() { 29 | JsonNode node = config.findPath(path); 30 | 31 | return node.isMissingNode() ? defaultValue : OBJECT_MAPPER.convertValue(node, configClass); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/config/LoggingConfigurer.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.config; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.nio.charset.Charset; 7 | import java.util.logging.LogManager; 8 | import java.util.logging.Logger; 9 | 10 | public class LoggingConfigurer { 11 | private static final Logger LOGGER = Logger.getLogger(LoggingConfigurer.class.getName()); 12 | 13 | public static void configure() throws SecurityException, IOException { 14 | try(FileInputStream stream = new FileInputStream("logging.properties")) { 15 | LogManager.getLogManager().readConfiguration(stream); 16 | } 17 | catch(FileNotFoundException e) { 18 | System.out.println("[INFO] File 'logging.properties' not found, using defaults"); 19 | } 20 | 21 | LOGGER.info("Logging configured"); 22 | LOGGER.info("Java version: " + System.getProperty("java.version")); 23 | LOGGER.info("JavaFX version: " + System.getProperty("javafx.runtime.version")); 24 | LOGGER.info("Default encoding: " + Charset.defaultCharset()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/dialog/Tasks.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.dialog; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import javafx.concurrent.Task; 6 | 7 | public class Tasks { 8 | public static Task of(Supplier supplier) { 9 | return new Task<>() { 10 | @Override 11 | protected T call() { 12 | updateTitle("Loading..."); 13 | 14 | return supplier.get(); 15 | } 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/dialog/dialogs.less: -------------------------------------------------------------------------------- 1 | @import "${root}/hs/mediasystem/runner/properties.less"; 2 | 3 | .dialog { 4 | .content { 5 | -fx-fill-width: true; 6 | } 7 | 8 | .title { 9 | .style-heading2; 10 | .heading-drop-shadow; 11 | -fx-text-fill: @text-primary-highlight; 12 | } 13 | 14 | .result-box { 15 | -fx-padding: 1em 0 0 0; 16 | -fx-spacing: 1em; 17 | } 18 | 19 | .progress-box { 20 | -fx-padding: 0.5em; 21 | 22 | .progress-bar { 23 | -fx-max-width: infinity; 24 | } 25 | } 26 | 27 | .exclamation { 28 | -fx-text-fill: transparent; 29 | -fx-background-color: @text-accent; 30 | .style-heading1; 31 | .black; 32 | -fx-effect: innershadow(three-pass-box, rgba(0, 0, 0, 0.8), 0.4em, 0.5, 0, 0); 33 | -fx-alignment: center; 34 | -fx-background-radius: 100; 35 | -fx-min-width: 1.5em; 36 | -fx-min-height: 1.5em; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/fonts-linux.less: -------------------------------------------------------------------------------- 1 | .light { 2 | -fx-font-family: "Noto Sans Light"; 3 | -fx-font-weight: normal; 4 | } 5 | 6 | .regular { 7 | -fx-font-family: "Noto Sans"; 8 | -fx-font-weight: normal; 9 | } 10 | 11 | .medium { 12 | -fx-font-family: "Noto Sans Medium"; 13 | -fx-font-weight: normal; 14 | } 15 | 16 | .semi-bold { 17 | -fx-font-family: "Noto Sans SemiBold"; 18 | -fx-font-weight: normal; 19 | } 20 | 21 | .bold { 22 | -fx-font-family: "Noto Sans"; 23 | -fx-font-weight: bold; 24 | } 25 | 26 | .black { 27 | -fx-font-family: "Noto Sans Black"; 28 | -fx-font-weight: normal; 29 | } 30 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/fonts.less: -------------------------------------------------------------------------------- 1 | /* 2 | * The Noto Sans font supports many variations. To get the correct variation 3 | * in JavaFX, select them as follows: 4 | * 5 | * - Black = Noto Sans Blk 6 | * - Bold = Noto Sans + font-weight: bold 7 | * - Semi Bold = Noto Sans SemBd 8 | * - Medium = Noto Sans Med 9 | * - Regular = Noto Sans + font-weight: normal 10 | * - Light = Noto Sans Light 11 | * 12 | * Note that these names are for Windows. Other platforms can use 13 | * slightly different names. 14 | */ 15 | 16 | .light { 17 | -fx-font-family: "Noto Sans Light"; 18 | -fx-font-weight: normal; 19 | } 20 | 21 | .regular { 22 | -fx-font-family: "Noto Sans"; 23 | -fx-font-weight: normal; 24 | } 25 | 26 | .medium { 27 | -fx-font-family: "Noto Sans Med"; 28 | -fx-font-weight: bold; 29 | } 30 | 31 | .semi-bold { 32 | -fx-font-family: "Noto Sans SemBd"; 33 | -fx-font-weight: bold; 34 | } 35 | 36 | .bold { 37 | -fx-font-family: "Noto Sans"; 38 | -fx-font-weight: bold; 39 | } 40 | 41 | .black { 42 | -fx-font-family: "Noto Sans Blk"; 43 | -fx-font-weight: bold; 44 | } 45 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/grouping/Grouping.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.grouping; 2 | 3 | import java.util.List; 4 | 5 | public interface Grouping { 6 | List group(List items); 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/grouping/NoGrouping.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.grouping; 2 | 3 | import java.util.List; 4 | 5 | public class NoGrouping implements Grouping { 6 | 7 | @SuppressWarnings("unchecked") 8 | @Override 9 | public List group(List items) { 10 | return (List)items; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/grouping/WorksGroup.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.grouping; 2 | 3 | import hs.mediasystem.domain.work.WorkId; 4 | import hs.mediasystem.ui.api.domain.Details; 5 | import hs.mediasystem.ui.api.domain.Work; 6 | 7 | import java.util.List; 8 | 9 | public class WorksGroup { 10 | private final WorkId id; 11 | private final Details details; 12 | private final List children; 13 | private final boolean allWatched; 14 | 15 | public WorksGroup(WorkId id, Details details, List children, boolean allWatched) { 16 | if(id == null) { 17 | throw new IllegalArgumentException("id cannot be null"); 18 | } 19 | if(details == null) { 20 | throw new IllegalArgumentException("details cannot be null"); 21 | } 22 | if(children == null) { 23 | throw new IllegalArgumentException("children cannot be null"); 24 | } 25 | 26 | this.id = id; 27 | this.details = details; 28 | this.children = children; 29 | this.allWatched = allWatched; 30 | } 31 | 32 | public WorkId getId() { 33 | return id; 34 | } 35 | 36 | public Details getDetails() { 37 | return details; 38 | } 39 | 40 | public List getChildren() { 41 | return children; 42 | } 43 | 44 | public boolean allWatched() { 45 | return allWatched; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/presentation/PresentationLoader.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.presentation; 2 | 3 | import hs.mediasystem.presentation.NavigateEvent; 4 | import hs.mediasystem.presentation.Presentation; 5 | import hs.mediasystem.runner.dialog.Dialogs; 6 | 7 | import java.util.function.Supplier; 8 | 9 | import javafx.concurrent.Task; 10 | import javafx.event.Event; 11 | 12 | public class PresentationLoader { 13 | 14 | public static void navigate(Event event, Supplier presentationSupplier) { 15 | navigate(event, new Task() { 16 | @Override 17 | protected T call() { 18 | updateTitle("Loading..."); 19 | 20 | return presentationSupplier.get(); 21 | } 22 | }); 23 | } 24 | 25 | public static void navigate(Event event, Task task) { 26 | Dialogs.showProgressDialog(event, task).ifPresent(p -> Event.fireEvent(event.getTarget(), NavigateEvent.to(p))); 27 | 28 | event.consume(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/presentation/Presentations.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.presentation; 2 | 3 | import hs.mediasystem.presentation.PresentationEvent; 4 | import hs.mediasystem.runner.dialog.Dialogs; 5 | 6 | import javafx.event.Event; 7 | import javafx.scene.Node; 8 | 9 | public class Presentations { 10 | 11 | public static void showWindow(Event event, Node content) { 12 | Dialogs.show(event, "", content); 13 | 14 | if(event.getTarget() instanceof Node node) { 15 | node.fireEvent(PresentationEvent.refresh()); 16 | } 17 | } 18 | 19 | public static void showDialog(Event event, Node content) { 20 | Dialogs.show(event, content); 21 | 22 | if(event.getTarget() instanceof Node node) { 23 | node.fireEvent(PresentationEvent.refresh()); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/presentation/ViewPortFactory.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.presentation; 2 | 3 | import hs.mediasystem.presentation.ParentPresentation; 4 | import hs.mediasystem.presentation.Theme; 5 | 6 | import java.util.function.Consumer; 7 | 8 | import javafx.scene.Node; 9 | 10 | import javax.inject.Inject; 11 | import javax.inject.Singleton; 12 | 13 | @Singleton 14 | public class ViewPortFactory { 15 | @Inject private Theme theme; 16 | 17 | public ViewPort create(ParentPresentation presentation, Consumer nodeAdjuster) { 18 | return ViewPort.ofPresentation(theme, presentation, nodeAdjuster); 19 | } 20 | 21 | public ViewPort create(ParentPresentation presentation) { 22 | return create(presentation, null); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/root/ParentalControlsProvider.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.root; 2 | 3 | import hs.mediasystem.runner.config.ConfigurationProvider; 4 | import hs.mediasystem.runner.root.ParentalControlsProvider.ParentalControls; 5 | 6 | import java.util.List; 7 | 8 | import javax.inject.Singleton; 9 | 10 | @Singleton 11 | public class ParentalControlsProvider extends ConfigurationProvider { 12 | private static final ParentalControls DEFAULT = new ParentalControls(null, null, List.of()); 13 | 14 | public ParentalControlsProvider() { 15 | super(ParentalControls.class, "parental-controls", DEFAULT); 16 | } 17 | 18 | public static class ParentalControls { 19 | public final String passcode; 20 | public final int timeout; 21 | public final List hidden; 22 | 23 | public ParentalControls(String passcode, Integer timeout, List hidden) { 24 | this.passcode = passcode; 25 | this.timeout = timeout == null ? 900 : timeout; 26 | this.hidden = List.copyOf(hidden); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/root/RootPresentation.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.root; 2 | 3 | import hs.mediasystem.presentation.ParentPresentation; 4 | import hs.mediasystem.runner.util.debug.DebugFX; 5 | 6 | import javafx.beans.property.BooleanProperty; 7 | import javafx.beans.property.SimpleBooleanProperty; 8 | 9 | import javax.inject.Singleton; 10 | 11 | @Singleton 12 | public class RootPresentation extends ParentPresentation { 13 | public final BooleanProperty clockVisible = new SimpleBooleanProperty(true); 14 | public final BooleanProperty hiddenItemsVisible = new SimpleBooleanProperty(false); 15 | public final BooleanProperty fpsGraphVisible = new SimpleBooleanProperty(false); 16 | 17 | public void toggleDebug() { 18 | boolean enabled = !DebugFX.getEnabled(); 19 | 20 | System.out.println("Setting continuous debug checking to " + (enabled ? "ENABLED" : "DISABLED")); 21 | 22 | DebugFX.setEnabled(enabled); 23 | } 24 | 25 | public void debugOnce() { 26 | System.out.println("Checking references once..."); 27 | 28 | DebugFX.checkReferences(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/root/RootPresentation.yaml: -------------------------------------------------------------------------------- 1 | section: 2 | label: General 3 | 4 | action-target: 5 | debugOnce: 6 | label: Debug Once 7 | visible: false 8 | fpsGraphVisible: 9 | label: Show FPS graph -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/root/action-display-popup.less: -------------------------------------------------------------------------------- 1 | @import "hs/mediasystem/runner/util/global.less"; 2 | 3 | // hbox 4 | .action-display-popup 5 | { 6 | -fx-background-color: @glass-color; 7 | -fx-background-radius: 0.5em; 8 | -fx-alignment: CENTER; 9 | -fx-spacing: 1em; 10 | -fx-padding: 1em; 11 | 12 | // duplicated from option-menu-dialog 13 | .slider-container { 14 | .slider { 15 | -fx-min-width: 25em; 16 | } 17 | 18 | .slider-value { 19 | -fx-min-width: 10em; 20 | } 21 | 22 | -fx-spacing: 10px; 23 | } 24 | 25 | .spinner { 26 | -fx-min-width: 25em; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/root/clock-pane.less: -------------------------------------------------------------------------------- 1 | @import "hs/mediasystem/runner/properties.less"; 2 | 3 | .clock-pane 4 | { 5 | -fx-alignment: bottom-right; 6 | -fx-padding: 5; 7 | 8 | .clock-box 9 | { 10 | -fx-padding: 5 15 5 15; 11 | -fx-alignment: center; 12 | 13 | .clock 14 | { 15 | .style-p1-extra-light; 16 | } 17 | 18 | .date 19 | { 20 | .style-p3; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/root/fps-pane.less: -------------------------------------------------------------------------------- 1 | @import "hs/mediasystem/runner/properties.less"; 2 | 3 | .fps-pane 4 | { 5 | -fx-alignment: top-right; 6 | -fx-padding: 5; 7 | -fx-background-color: @glass-color; 8 | } 9 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/root/logo-pane.less: -------------------------------------------------------------------------------- 1 | @import "hs/mediasystem/runner/properties.less"; 2 | 3 | .logo-pane 4 | { 5 | -fx-alignment: bottom-center; 6 | -fx-padding: 10; 7 | 8 | .program-name .label { 9 | -fx-text-fill: white; 10 | } 11 | 12 | .program-name .left { 13 | -fx-font-size: 13px; 14 | -fx-translate-x: -1; 15 | -fx-translate-y: -4; 16 | } 17 | 18 | .program-name .center { 19 | -fx-label-padding: 0 2 0 1; 20 | -fx-font-size: 20px; 21 | -fx-font-weight: bold; 22 | -fx-font-style: italic; 23 | -fx-scale-x: 2.0; 24 | -fx-scale-y: 2.0; 25 | } 26 | 27 | .program-name .right { 28 | -fx-font-size: 13px; 29 | -fx-translate-y: 2; 30 | } 31 | } 32 | 33 | .element { 34 | -fx-alignment: bottom-center; 35 | } -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/root/progress-pane.less: -------------------------------------------------------------------------------- 1 | @import "${root}/hs/mediasystem/runner/properties.less"; 2 | 3 | .progress-pane { 4 | -fx-padding: 10; 5 | 6 | .vbox { 7 | -fx-alignment: bottom-center; 8 | 9 | .progress-bar { 10 | .bar { 11 | -fx-padding: 1px; 12 | } 13 | } 14 | 15 | .progress-text { 16 | .style-p3; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/root/root.less: -------------------------------------------------------------------------------- 1 | .fps-layer { 2 | -fx-alignment: top-right; 3 | -fx-padding: 10; 4 | } -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/util/DelegatingComparator.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.util; 2 | 3 | import java.util.Comparator; 4 | import java.util.function.Predicate; 5 | 6 | public class DelegatingComparator implements Comparator { 7 | private final Predicate usePrimary; 8 | private final Comparator primaryComparator; 9 | private final Comparator secondaryComparator; 10 | 11 | public DelegatingComparator(Predicate usePrimary, Comparator primaryComparator, Comparator secondaryComparator) { 12 | this.usePrimary = usePrimary; 13 | this.primaryComparator = primaryComparator; 14 | this.secondaryComparator = secondaryComparator; 15 | } 16 | 17 | @Override 18 | public int compare(T o1, T o2) { 19 | if(usePrimary.test(o1) || usePrimary.test(o2)) { 20 | return primaryComparator.compare(o1, o2); 21 | } 22 | 23 | return secondaryComparator.compare(o1, o2); 24 | } 25 | } -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/util/SceneManager.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.util; 2 | 3 | import hs.mediasystem.runner.util.FXSceneManager.SceneLayout; 4 | 5 | import javafx.scene.Scene; 6 | import javafx.scene.layout.StackPane; 7 | import javafx.stage.Screen; 8 | 9 | public interface SceneManager { 10 | StackPane getRootPane(); 11 | Scene getScene(); 12 | void setSceneLayout(SceneLayout sceneLayout); 13 | void setPlayerRoot(Object root); 14 | void disposePlayerRoot(); 15 | 16 | Screen getScreen(); 17 | int getScreenNumber(); 18 | void setScreenNumber(int screenNumber); 19 | void display(); 20 | } 21 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/util/action/Action.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.util.action; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * A location and descriptor for an action. 7 | */ 8 | public class Action { 9 | private final String path; 10 | private final String descriptor; 11 | 12 | /** 13 | * Constructs a new instance. 14 | * 15 | * @param path a path identifying the location of the action, cannot be {@code null} 16 | * @param descriptor a descriptor describing the action, cannot be {@code null} 17 | */ 18 | public Action(String path, String descriptor) { 19 | this.path = Objects.requireNonNull(path, "path"); 20 | this.descriptor = Objects.requireNonNull(descriptor, "descriptor"); 21 | } 22 | 23 | /** 24 | * Returns the action descriptor. 25 | * 26 | * @return the action descriptor, never {@code null} 27 | */ 28 | public String getDescriptor() { 29 | return descriptor; 30 | } 31 | 32 | /** 33 | * Returns the location of the action. 34 | * 35 | * @return the location of the action, never {@code null} 36 | */ 37 | public String getPath() { 38 | return path; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return path + "#" + descriptor; 44 | } 45 | } -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/util/grid/MediaItemFormatter.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.util.grid; 2 | 3 | import java.time.LocalDate; 4 | import java.time.format.DateTimeFormatter; 5 | import java.time.format.FormatStyle; 6 | 7 | import javafx.beans.binding.StringBinding; 8 | import javafx.beans.value.ObservableValue; 9 | 10 | public class MediaItemFormatter { 11 | private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM); 12 | 13 | public static StringBinding formattedLocalDate(final ObservableValue date) { 14 | return new StringBinding() { 15 | { 16 | bind(date); 17 | } 18 | 19 | @Override 20 | protected String computeValue() { 21 | return formattedLocalDate(date.getValue()); 22 | } 23 | }; 24 | } 25 | 26 | public static String formattedLocalDate(LocalDate date) { 27 | return date == null ? null : DATE_TIME_FORMATTER.format(date); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/util/grid/MediaStatus.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.runner.util.grid; 2 | 3 | public enum MediaStatus { 4 | UNAVAILABLE, 5 | AVAILABLE, 6 | WATCHED 7 | } -------------------------------------------------------------------------------- /mediasystem-runner/src/main/java/hs/mediasystem/runner/util/markdown-styles.less: -------------------------------------------------------------------------------- 1 | @import "${root}/hs/mediasystem/runner/properties.less"; 2 | 3 | .styled-text-area { 4 | -fx-background-color: transparent; 5 | } 6 | 7 | .text { 8 | -fx-fill: @text-primary; 9 | } 10 | 11 | .heading1 { -fx-font-size: 36px; } 12 | .heading2 { -fx-font-size: 30px; } 13 | .heading3 { -fx-font-size: 25px; } 14 | .heading4 { -fx-font-size: 21px; } 15 | 16 | .text { 17 | &.heading1, &.heading2, &.heading3, &.heading4 { 18 | -fx-fill: @text-primary-highlight; 19 | } 20 | 21 | &.code { -fx-font-family: monospaced; } 22 | &.emphasis { -fx-font-style: italic; } 23 | &.strong { .black; } 24 | } 25 | -------------------------------------------------------------------------------- /mediasystem-runner/src/test/java/hs/mediasystem/ArchitectureTest.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem; 2 | 3 | import com.tngtech.archunit.core.importer.ImportOption; 4 | import com.tngtech.archunit.junit.AnalyzeClasses; 5 | import com.tngtech.archunit.junit.ArchTest; 6 | import com.tngtech.archunit.lang.ArchRule; 7 | import com.tngtech.archunit.library.DependencyRules; 8 | 9 | import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; 10 | 11 | @AnalyzeClasses(packages = {"hs.mediasystem.runner", "hs.mediasystem.presentation", "hs.mediasystem.plugin"}, importOptions = ImportOption.DoNotIncludeTests.class) 12 | public class ArchitectureTest { 13 | 14 | @ArchTest 15 | private final ArchRule packagesShouldBeFreeOfCycles = slices().matching("(**)").should().beFreeOfCycles(); 16 | 17 | @ArchTest 18 | private final ArchRule noClassesShouldDependOnUpperPackages = DependencyRules.NO_CLASSES_SHOULD_DEPEND_UPPER_PACKAGES; 19 | } 20 | -------------------------------------------------------------------------------- /mediasystem-ui-api/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /mediasystem-ui-api/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | hs.mediasystem 8 | mediasystem-parent 9 | ${revision} 10 | 11 | 12 | mediasystem-ui-api 13 | 14 | 15 | 16 | hs.mediasystem 17 | mediasystem-util 18 | ${revision} 19 | 20 | 21 | hs.mediasystem 22 | mediasystem-domain 23 | ${revision} 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/CollectionClient.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api; 2 | 3 | import hs.mediasystem.domain.work.Collection; 4 | 5 | import java.util.List; 6 | 7 | public interface CollectionClient { 8 | List findCollections(); 9 | } 10 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/ConsumedStateChanged.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api; 2 | 3 | import hs.mediasystem.domain.stream.ContentID; 4 | 5 | import javafx.event.Event; 6 | import javafx.event.EventType; 7 | 8 | public class ConsumedStateChanged extends Event { 9 | public static final EventType ANY = new EventType<>(EventType.ROOT, "CONSUMED_STATE"); 10 | 11 | private final ContentID contentId; 12 | private final boolean consumed; 13 | 14 | public ConsumedStateChanged(ContentID contentId, boolean consumed) { 15 | super(ConsumedStateChanged.ANY); 16 | 17 | this.contentId = contentId; 18 | this.consumed = consumed; 19 | } 20 | 21 | public ContentID getContentId() { 22 | return contentId; 23 | } 24 | 25 | public boolean isContentConsumed() { 26 | return consumed; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/ImageClient.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api; 2 | 3 | import java.io.IOException; 4 | import java.util.Optional; 5 | 6 | public interface ImageClient { 7 | Optional findImage(String id) throws IOException; 8 | } 9 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/PersonClient.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api; 2 | 3 | import hs.mediasystem.domain.work.PersonId; 4 | import hs.mediasystem.ui.api.domain.Person; 5 | 6 | import java.util.Optional; 7 | 8 | public interface PersonClient { 9 | Optional findPerson(PersonId id); 10 | } 11 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/RecommendationClient.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api; 2 | 3 | import hs.mediasystem.domain.stream.MediaType; 4 | import hs.mediasystem.ui.api.domain.Recommendation; 5 | 6 | import java.util.List; 7 | import java.util.function.Predicate; 8 | 9 | public interface RecommendationClient { 10 | List findRecommendations(int maximum); 11 | List findNew(Predicate filter); 12 | } 13 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/SettingsClient.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api; 2 | 3 | public interface SettingsClient { 4 | SettingsSource of(String key); 5 | } 6 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/SettingsSource.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api; 2 | 3 | public interface SettingsSource { 4 | String getSetting(String name); 5 | String getSettingOrDefault(String name, String defaultValue); 6 | int getIntSettingOrDefault(String name, int defaultValue, int min, int max); 7 | void storeSetting(String name, String value); 8 | void storeIntSetting(String name, int value); 9 | } 10 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/StreamStateClient.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api; 2 | 3 | import hs.mediasystem.domain.stream.ContentID; 4 | 5 | import java.time.Duration; 6 | import java.time.Instant; 7 | 8 | public interface StreamStateClient { 9 | boolean isConsumed(ContentID contentId); 10 | void setConsumed(ContentID contentId, boolean consumed); 11 | 12 | Instant getLastConsumptionTime(ContentID contentId); 13 | void setLastConsumptionTime(ContentID contentId, Instant time); 14 | 15 | Duration getResumePosition(ContentID contentId); 16 | void setResumePosition(ContentID contentId, Duration duration); 17 | } 18 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/WorkClient.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api; 2 | 3 | import hs.mediasystem.domain.work.VideoLink; 4 | import hs.mediasystem.domain.work.WorkId; 5 | import hs.mediasystem.ui.api.domain.Contribution; 6 | import hs.mediasystem.ui.api.domain.Work; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | public interface WorkClient { 12 | List findChildren(WorkId workId); 13 | Optional find(WorkId workId); 14 | List findRecommendations(WorkId workId); 15 | List findContributions(WorkId id); 16 | List findVideoLinks(WorkId id); 17 | void reidentify(WorkId id); 18 | } 19 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/WorksClient.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api; 2 | 3 | import hs.mediasystem.domain.stream.MediaType; 4 | import hs.mediasystem.ui.api.domain.Work; 5 | 6 | import java.util.List; 7 | import java.util.function.Predicate; 8 | 9 | public interface WorksClient { 10 | List findNewest(int maximum, Predicate filter); 11 | List findAllByType(MediaType type, String tag); 12 | List findRootsByTag(String tag); 13 | List findTop100(); 14 | } 15 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/domain/Classification.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.domain; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | 7 | public record Classification(List keywords, List genres, List languages, Map contentRatings, Boolean pornographic, Optional stage) { 8 | public static final Classification DEFAULT = new Classification(List.of(), List.of(), List.of(), Map.of(), null, Optional.empty()); 9 | } 10 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/domain/Context.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.domain; 2 | 3 | import hs.mediasystem.domain.stream.MediaType; 4 | import hs.mediasystem.domain.work.WorkId; 5 | import hs.mediasystem.util.image.ImageHandle; 6 | 7 | import java.util.Optional; 8 | 9 | public record Context(WorkId id, String title, Optional cover, Optional backdrop) { 10 | public MediaType type() { 11 | return id.getType(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/domain/Contribution.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.domain; 2 | 3 | public record Contribution(Person person, Role role, double order) { 4 | public Contribution { 5 | if(person == null) { 6 | throw new IllegalArgumentException("person cannot be null"); 7 | } 8 | if(role == null) { 9 | throw new IllegalArgumentException("role cannot be null"); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/domain/Participation.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.domain; 2 | 3 | public record Participation(Role role, Work work, int episodeCount, double popularity) { 4 | } 5 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/domain/Recommendation.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.domain; 2 | 3 | import java.time.Instant; 4 | 5 | /** 6 | * Represents a Work that is recommended based on some criteria.

7 | * 8 | * The recommended work may be recommended based on another work. Therefore 9 | * the sample time may not match the last time watched. 10 | * 11 | * @param work the recommended {@link Work}, never {@code null} 12 | * @param sampleTime the relevant time for the recommendation, never {@code null}. 13 | * In case of a partially watched recommendation, this is the time it was last 14 | * watched. In case of a next in sequence recommendation, this is the time the 15 | * previous item in the sequence was watched and in case of a new recommendation, 16 | * this is the time when the new item was discovered. 17 | */ 18 | public record Recommendation(Work work, Instant sampleTime) { 19 | public Recommendation { 20 | if(work == null) { 21 | throw new IllegalArgumentException("work cannot be null"); 22 | } 23 | if(sampleTime == null) { 24 | throw new IllegalArgumentException("sampleTime cannot be null"); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/domain/Role.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.domain; 2 | 3 | public record Role(Type type, String character, String job) { 4 | public enum Type {CAST, CREW, GUEST_STAR} 5 | } 6 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/domain/Sequence.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.domain; 2 | 3 | import java.util.Comparator; 4 | import java.util.Optional; 5 | 6 | public record Sequence(Type type, int number, Optional seasonNumber) { 7 | public static final Comparator COMPARATOR = Comparator 8 | .comparingInt((Sequence o) -> o.type().ordinal()) 9 | .thenComparingInt((Sequence o) -> o.seasonNumber().orElse(0)) 10 | .thenComparingInt((Sequence o) -> o.number()); 11 | 12 | public enum Type {EPISODE, SPECIAL, EXTRA} 13 | 14 | public Sequence { 15 | if(type == null) { 16 | throw new IllegalArgumentException("type cannot be null"); 17 | } 18 | if(number < 0) { 19 | throw new IllegalArgumentException("number must not be negative: " + number); 20 | } 21 | if(seasonNumber == null) { 22 | throw new IllegalArgumentException("seasonNumber cannot be null"); 23 | } 24 | 25 | seasonNumber.ifPresent(sn -> { 26 | if(sn <= 0) { 27 | throw new IllegalArgumentException("seasonNumber must be positive: " + seasonNumber); 28 | } 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/domain/Serie.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.domain; 2 | 3 | import java.time.LocalDate; 4 | import java.util.Optional; 5 | 6 | public record Serie(Optional lastAirDate, Optional totalSeasons, Optional totalEpisodes) { 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/domain/Stage.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.domain; 2 | 3 | public enum Stage { 4 | PLANNED, 5 | IN_PRODUCTION, 6 | RELEASED, // When first episode is aired, or final stage for movies 7 | ENDED // When serie has ended 8 | } 9 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/domain/State.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.domain; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | import java.util.Comparator; 6 | import java.util.Optional; 7 | 8 | public record State(Optional lastConsumptionTime, boolean consumed, Duration resumePosition) { 9 | public static final Comparator WATCHED_DATE_REVERSED = Comparator.comparing((State s) -> s.lastConsumptionTime().orElse(null), Comparator.nullsLast(Comparator.naturalOrder())).reversed(); 10 | public static final State EMPTY = new State(null, false, Duration.ZERO); 11 | 12 | public State { 13 | if(resumePosition == null) { 14 | throw new IllegalArgumentException("resumePosition cannot be null"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/player/AudioTrack.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.player; 2 | 3 | public class AudioTrack { 4 | public static final AudioTrack NO_AUDIO_TRACK = new AudioTrack(-1, "Disabled"); 5 | 6 | private final int id; 7 | private final String description; 8 | 9 | public AudioTrack(int id, String description) { 10 | this.id = id; 11 | this.description = description; 12 | } 13 | 14 | public String getDescription() { 15 | return description; 16 | } 17 | 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return "('" + description + "', AudioTrack[id=" + id + "])"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/player/NativePlayerInitializationException.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.player; 2 | 3 | import hs.mediasystem.util.Localizable; 4 | import hs.mediasystem.util.exception.Throwables; 5 | 6 | public class NativePlayerInitializationException extends RuntimeException implements Localizable { 7 | 8 | public NativePlayerInitializationException(Throwable cause) { 9 | super(cause); 10 | } 11 | 12 | @Override 13 | public String toLocalizedString() { 14 | boolean is32bit = System.getProperty("os.arch").equals("x86"); 15 | 16 | return "### Unable to load installed video player\n" 17 | + "MediaSystem was unable to start playing the selected content because the " 18 | + "configured video player could not be initialized.\n" 19 | + "#### Solution\n" 20 | + "Please install a supported " + (is32bit ? "32" : "64") + " bit video player on your system and configure " 21 | + "MediaSystem with the correct player in `mediasystem.yaml`.\n" 22 | + "#### Technical details\n" 23 | + "```\n" 24 | + Throwables.toString(getCause()) 25 | + "```\n"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/player/PlayerEvent.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.player; 2 | 3 | import javafx.event.Event; 4 | import javafx.event.EventType; 5 | 6 | public class PlayerEvent extends Event { 7 | public enum Type {FINISHED} 8 | 9 | private final Type type; 10 | 11 | public PlayerEvent(Type type) { 12 | super(EventType.ROOT); 13 | this.type = type; 14 | } 15 | 16 | public Type getType() { 17 | return type; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/player/PlayerFactory.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.player; 2 | 3 | public interface PlayerFactory { 4 | public enum IntegrationMethod { 5 | WINDOW, 6 | PIXEL_BUFFER 7 | } 8 | 9 | String getName(); 10 | IntegrationMethod getIntegrationMethod(); 11 | PlayerPresentation create(); 12 | } 13 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/player/PlayerPresentation.yaml: -------------------------------------------------------------------------------- 1 | action-target: 2 | brightness: 3 | order: 1 4 | label: Brightness 5 | rate: 6 | order: 2 7 | label: Playback Speed 8 | audioTrack: 9 | order: 5 10 | label: Audio Track 11 | audioDelay: 12 | order: 6 13 | label: Audio Delay 14 | volume: 15 | order: 7 16 | label: Volume 17 | muted: 18 | order: 8 19 | label: Mute 20 | statOverlay: 21 | order: 9 22 | label: Statistics Overlay 23 | position: 24 | label: Position 25 | visible: false 26 | paused: 27 | label: Pause 28 | visible: false 29 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/player/PlayerWindowIdSupplier.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.player; 2 | 3 | /** 4 | * Supplies a native window id for video players. 5 | */ 6 | public interface PlayerWindowIdSupplier { 7 | long getWindowId(); 8 | } 9 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/player/StatOverlay.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.player; 2 | 3 | public class StatOverlay { 4 | public static final StatOverlay DISABLED = new StatOverlay(-1, "Disabled"); 5 | 6 | private final int id; 7 | private final String description; 8 | 9 | public StatOverlay(int id, String description) { 10 | this.id = id; 11 | this.description = description; 12 | } 13 | 14 | public String getDescription() { 15 | return description; 16 | } 17 | 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | @Override 23 | public int hashCode() { 24 | return id; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object obj) { 29 | if (this == obj) { 30 | return true; 31 | } 32 | if (obj == null || getClass() != obj.getClass()) { 33 | return false; 34 | } 35 | 36 | StatOverlay other = (StatOverlay) obj; 37 | 38 | return id == other.id; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "('" + description + "', StatOverlay[id=" + id + "])"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/player/Subtitle.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.player; 2 | 3 | public class Subtitle { 4 | public static final Subtitle DISABLED = new Subtitle(-1, "Disabled"); 5 | 6 | private final int id; 7 | private final String description; 8 | 9 | public Subtitle(int id, String description) { 10 | this.id = id; 11 | this.description = description; 12 | } 13 | 14 | public String getDescription() { 15 | return description; 16 | } 17 | 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | @Override 23 | public int hashCode() { 24 | return id; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object obj) { 29 | if (this == obj) { 30 | return true; 31 | } 32 | if (obj == null || getClass() != obj.getClass()) { 33 | return false; 34 | } 35 | 36 | Subtitle other = (Subtitle) obj; 37 | 38 | return id == other.id; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "('" + description + "', Subtitle[id=" + id + "])"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/player/SubtitlePresentation.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.ui.api.player; 2 | 3 | import javafx.beans.property.LongProperty; 4 | import javafx.beans.property.Property; 5 | import javafx.collections.ObservableList; 6 | 7 | public interface SubtitlePresentation { 8 | 9 | /** 10 | * Returns the current subtitle delay in milliseconds. 11 | * 12 | * @return the current subtitle delay in milliseconds 13 | */ 14 | LongProperty subtitleDelayProperty(); 15 | 16 | /** 17 | * Returns the current subtitle. Will return a Subtitle.DISABLED when not showing any 18 | * subtitle. 19 | * 20 | * @return the current subtitle 21 | */ 22 | Property subtitleProperty(); 23 | ObservableList subtitles(); 24 | } 25 | -------------------------------------------------------------------------------- /mediasystem-ui-api/src/main/java/hs/mediasystem/ui/api/player/SubtitlePresentation.yaml: -------------------------------------------------------------------------------- 1 | action-target: 2 | subtitle: 3 | order: 3 4 | label: Subtitle 5 | subtitleDelay: 6 | order: 4 7 | label: Subtitle Delay 8 | -------------------------------------------------------------------------------- /mediasystem-util/.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /mediasystem-util.iml 3 | /target/ 4 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/AutoReentrantLock.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util; 2 | 3 | import java.util.concurrent.locks.ReentrantLock; 4 | 5 | public class AutoReentrantLock { 6 | private final ReentrantLock reentrantLock = new ReentrantLock(); 7 | 8 | /** 9 | * Obtains the lock, returning a {@link Key} that can be used to 10 | * release the lock again (early) and/or automatically release it when 11 | * used in a try-with-resource block. 12 | * 13 | * @return a {@link Key}, never null 14 | */ 15 | public Key lock() { 16 | return new Key(); 17 | } 18 | 19 | public class Key implements AutoCloseable { 20 | private boolean alreadyClosed; 21 | 22 | private Key() { 23 | reentrantLock.lock(); 24 | } 25 | 26 | public void earlyUnlock() { 27 | close(); 28 | } 29 | 30 | @Override 31 | public void close() { 32 | if(!alreadyClosed) { 33 | reentrantLock.unlock(); 34 | alreadyClosed = true; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/Localizable.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util; 2 | 3 | public interface Localizable { 4 | String toLocalizedString(); 5 | } 6 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/checked/CheckedStreams.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.checked; 2 | 3 | import java.io.IOException; 4 | import java.util.Collection; 5 | import java.util.stream.Stream; 6 | 7 | public class CheckedStreams { 8 | 9 | public static Stream0 of(Collection collection) { 10 | return new Stream0<>(collection.stream()); 11 | } 12 | 13 | public static Stream1 forIOException(Collection collection) { 14 | return new Stream1<>(collection.stream(), IOException.class); 15 | } 16 | 17 | public static Stream0 of(Stream stream) { 18 | return new Stream0<>(stream); 19 | } 20 | 21 | public static Stream1 forIOException(Stream stream) { 22 | return new Stream1<>(stream, IOException.class); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/checked/Stream0.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.checked; 2 | 3 | import java.util.Collection; 4 | import java.util.stream.Stream; 5 | 6 | public class Stream0 extends Flow { 7 | 8 | public static Stream0 of(Stream stream) { 9 | return new Stream0<>(stream); 10 | } 11 | 12 | public static Stream0 of(Collection collection) { 13 | return new Stream0<>(collection.stream()); 14 | } 15 | 16 | protected Stream0(Stream stream) { 17 | super(stream, new Class[] {}); 18 | } 19 | 20 | public Stream1 declaring(Class exceptionType) { 21 | return new Stream1<>(stream, exceptionType); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/checked/Stream1.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.checked; 2 | 3 | import java.util.stream.Stream; 4 | 5 | public class Stream1 extends Flow { 6 | 7 | protected Stream1(Stream stream, Class exceptionType) { 8 | super(stream, new Class[] {exceptionType}); 9 | } 10 | 11 | @SuppressWarnings("unchecked") 12 | Stream2 declaring(Class exceptionType) { 13 | return new Stream2<>(stream, (Class)exceptionTypes[0], exceptionType); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/checked/Stream2.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.checked; 2 | 3 | import java.util.stream.Stream; 4 | 5 | public class Stream2 extends Flow { 6 | 7 | protected Stream2(Stream stream, Class exceptionType1, Class exceptionType2) { 8 | super(stream, new Class[] {exceptionType1, exceptionType2}); 9 | } 10 | 11 | @SuppressWarnings("unchecked") 12 | Stream3 declaring(Class exceptionType) { 13 | return new Stream3<>(stream, (Class)exceptionTypes[0], (Class)exceptionTypes[1], exceptionType); 14 | } 15 | } -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/checked/Stream3.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.checked; 2 | 3 | import java.util.stream.Stream; 4 | 5 | public class Stream3 extends Flow { 6 | 7 | protected Stream3(Stream stream, Class exceptionType1, Class exceptionType2, Class exceptionType3) { 8 | super(stream, new Class[] {exceptionType1, exceptionType2, exceptionType3}); 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/checked/ThrowingConsumer.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.checked; 2 | 3 | public interface ThrowingConsumer { 4 | void accept(T t) throws E1, E2, E3; 5 | } 6 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/checked/ThrowingFunction.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.checked; 2 | 3 | public interface ThrowingFunction { 4 | R apply(T t) throws E1, E2, E3; 5 | } -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/checked/ThrowingPredicate.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.checked; 2 | 3 | public interface ThrowingPredicate { 4 | boolean test(T t) throws E1, E2, E3; 5 | } 6 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/checked/ThrowingSupplier.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.checked; 2 | 3 | public interface ThrowingSupplier { 4 | T get() throws E1, E2, E3; 5 | } 6 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/concurrent/AutoSemaphore.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.concurrent; 2 | 3 | import hs.mediasystem.util.checked.ThrowingSupplier; 4 | 5 | import java.util.concurrent.Semaphore; 6 | 7 | public class AutoSemaphore extends Semaphore { 8 | 9 | public AutoSemaphore(int permits) { 10 | super(permits); 11 | } 12 | 13 | public T execute(ThrowingSupplier supplier) throws InterruptedException, E1, E2, E3 { 14 | acquire(); 15 | 16 | try { 17 | return supplier.get(); 18 | } 19 | finally { 20 | release(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/concurrent/NamedExecutors.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.concurrent; 2 | 3 | import java.util.Objects; 4 | import java.util.concurrent.Executor; 5 | import java.util.concurrent.SynchronousQueue; 6 | import java.util.concurrent.ThreadPoolExecutor; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class NamedExecutors { 10 | 11 | /** 12 | * Creates an executor which executes a maximum of one task concurrently and silently 13 | * rejects all tasks when a task is running. 14 | * 15 | * @param name base name of the threads created, cannot be {@code null} 16 | * @return an {@link Executor}, never {@code null} 17 | */ 18 | public static Executor newSingleTaskExecutor(String name) { 19 | ThreadPoolExecutor executor = new ThreadPoolExecutor( 20 | 1, 1, 30, TimeUnit.SECONDS, 21 | new SynchronousQueue<>(), 22 | new NamedThreadFactory(Objects.requireNonNull(name, "name cannot be null"), true), 23 | new ThreadPoolExecutor.DiscardPolicy() 24 | ); 25 | 26 | executor.allowCoreThreadTimeOut(true); 27 | 28 | return executor; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/concurrent/NamedThreadFactory.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.concurrent; 2 | 3 | import java.util.concurrent.ThreadFactory; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | public class NamedThreadFactory implements ThreadFactory { 7 | private final AtomicInteger threadNumber = new AtomicInteger(0); 8 | 9 | private final String name; 10 | private final int priority; 11 | private final boolean daemon; 12 | 13 | public NamedThreadFactory(String name, int priority, boolean daemon) { 14 | this.name = name; 15 | this.daemon = daemon; 16 | this.priority = priority; 17 | } 18 | 19 | public NamedThreadFactory(String name) { 20 | this(name, Thread.NORM_PRIORITY); 21 | } 22 | 23 | public NamedThreadFactory(String name, boolean daemon) { 24 | this(name, Thread.NORM_PRIORITY, daemon); 25 | } 26 | 27 | public NamedThreadFactory(String name, int priority) { 28 | this(name, priority, false); 29 | } 30 | 31 | @Override 32 | public Thread newThread(Runnable r) { 33 | Thread thread = new Thread(r, String.format("%s-%d", name, threadNumber.incrementAndGet())); 34 | 35 | if(daemon != thread.isDaemon()) { 36 | thread.setDaemon(daemon); 37 | } 38 | 39 | if(priority != thread.getPriority()) { 40 | thread.setPriority(priority); 41 | } 42 | 43 | return thread; 44 | } 45 | } -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/exception/ExceptionalException.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.exception; 2 | 3 | import java.util.Objects; 4 | 5 | public class ExceptionalException extends RuntimeException { 6 | 7 | public ExceptionalException(Throwable cause) { 8 | super(cause); 9 | 10 | if(cause == null) { 11 | throw new IllegalArgumentException("cause cannot be null"); 12 | } 13 | } 14 | 15 | @Override 16 | public int hashCode() { 17 | return Objects.hash(getCause()); 18 | } 19 | 20 | @Override 21 | public boolean equals(Object obj) { 22 | if(this == obj) { 23 | return true; 24 | } 25 | if(obj == null || getClass() != obj.getClass()) { 26 | return false; 27 | } 28 | 29 | ExceptionalException other = (ExceptionalException)obj; 30 | 31 | if(!getCause().equals(other.getCause())) { 32 | return false; 33 | } 34 | 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/exception/HttpException.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.exception; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | 6 | public class HttpException extends IOException { 7 | private final int responseCode; 8 | 9 | public HttpException(URL url, int responseCode, String message) { 10 | super(url + " -> " + responseCode + ": " + message); 11 | 12 | this.responseCode = responseCode; 13 | } 14 | 15 | public int getResponseCode() { 16 | return responseCode; 17 | } 18 | } -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/exception/TryRunnable.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.exception; 2 | 3 | @FunctionalInterface 4 | public interface TryRunnable { 5 | void run() throws Throwable; 6 | } -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/exception/TrySupplier.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.exception; 2 | 3 | @FunctionalInterface 4 | public interface TrySupplier { 5 | T tryGet() throws Throwable; 6 | } -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/exception/WrappedCheckedException.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.exception; 2 | 3 | public class WrappedCheckedException extends RuntimeException { 4 | 5 | public WrappedCheckedException(Throwable throwable) { 6 | super(throwable); 7 | 8 | if(throwable instanceof RuntimeException) { 9 | throw new IllegalArgumentException("throwable must not be a RuntimeException: " + throwable); 10 | } 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/expose/AbstractExposedControl.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.expose; 2 | 3 | public abstract class AbstractExposedControl implements ExposedControl { 4 | protected String name; 5 | protected Class cls; 6 | 7 | @Override 8 | public String getName() { 9 | return name; 10 | } 11 | 12 | @Override 13 | public Class getDeclaringClass() { 14 | return cls; 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/expose/AbstractExposedProperty.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.expose; 2 | 3 | import java.util.ArrayList; 4 | import java.util.function.Function; 5 | 6 | import javafx.beans.property.Property; 7 | 8 | public abstract class AbstractExposedProperty extends AbstractExposedControl { 9 | protected Formatter formatter; 10 | 11 | private final Function> function; 12 | 13 | AbstractExposedProperty(Function> function) { 14 | this.function = function; 15 | } 16 | 17 | public Property getProperty(Object ownerInstance) { 18 | return function.apply(ownerInstance); 19 | } 20 | 21 | public Formatter getFormatter() { 22 | return formatter; 23 | } 24 | 25 | public class NameBuilder { 26 | public void as(String name) { 27 | AbstractExposedProperty.this.name = name; 28 | 29 | EXPOSED_PROPERTIES.computeIfAbsent(cls, k -> new ArrayList<>()).add(AbstractExposedProperty.this); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/expose/ExposedControl.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.expose; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public interface ExposedControl { 9 | static final Map, List> EXPOSED_PROPERTIES = new HashMap<>(); 10 | 11 | public enum Type {METHOD, BOOLEAN, NUMERIC, LIST} 12 | 13 | public static List find(Class cls) { 14 | return EXPOSED_PROPERTIES.getOrDefault(cls, Collections.emptyList()); 15 | } 16 | 17 | public static void clear() { 18 | EXPOSED_PROPERTIES.clear(); 19 | } 20 | 21 | String getName(); 22 | Class getDeclaringClass(); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/expose/ExposedMethod.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.expose; 2 | 3 | import java.util.ArrayList; 4 | import java.util.function.Function; 5 | 6 | public class ExposedMethod extends AbstractExposedControl { 7 | private final Function> function; 8 | 9 | public ExposedMethod(Function> function) { 10 | this.function = function; 11 | } 12 | 13 | /** 14 | * Returns a Trigger or null if the action is unavailable 15 | * right now. 16 | * 17 | * @param ownerInstance the instance which the method returning the trigger is part of 18 | * @return a {@link Trigger} or null if the action is unavailable 19 | */ 20 | public Trigger getTrigger(Object ownerInstance) { 21 | return function.apply(ownerInstance); 22 | } 23 | 24 | public class ActionParentBuilder { 25 | public NameBuilder of(Class cls) { 26 | ExposedMethod.this.cls = cls; 27 | 28 | return new NameBuilder(); 29 | } 30 | } 31 | 32 | public class NameBuilder { 33 | public void as(String name) { 34 | ExposedMethod.this.name = name; 35 | 36 | EXPOSED_PROPERTIES.computeIfAbsent(cls, k -> new ArrayList<>()).add(ExposedMethod.this); 37 | } 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/expose/ExposedNode.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.expose; 2 | 3 | import java.util.function.Function; 4 | 5 | import javafx.beans.property.Property; 6 | 7 | public class ExposedNode extends AbstractExposedProperty { 8 | private Class providedType; 9 | 10 | ExposedNode(Function> function) { 11 | super(function); 12 | } 13 | 14 | public Class getProvidedType() { 15 | return providedType; 16 | } 17 | 18 | public class ParentBuilder { 19 | public ProvidesBuilder of(Class cls) { 20 | ExposedNode.this.cls = cls; 21 | 22 | return new ProvidesBuilder(); 23 | } 24 | } 25 | 26 | public class ProvidesBuilder { 27 | public NameBuilder provides(Class cls) { 28 | ExposedNode.this.providedType = cls; 29 | 30 | return new NameBuilder(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/expose/Formatter.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.expose; 2 | 3 | public interface Formatter { 4 | String format(T value); 5 | } 6 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/image/ImageHandle.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.image; 2 | 3 | import java.io.IOException; 4 | 5 | public interface ImageHandle { 6 | 7 | /** 8 | * Returns the image data as a byte array. 9 | * 10 | * @return a byte array containing the image data, never null 11 | * @throws IOException when the data could not be obtained 12 | */ 13 | byte[] getImageData() throws IOException; 14 | 15 | String getKey(); 16 | boolean isFastSource(); 17 | } 18 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/image/ImageHandleFactory.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.image; 2 | 3 | import java.util.List; 4 | 5 | import javax.inject.Inject; 6 | import javax.inject.Singleton; 7 | 8 | @Singleton 9 | public class ImageHandleFactory { 10 | @Inject private List handlers; 11 | 12 | public ImageHandle fromURI(ImageURI uri) { 13 | if(uri == null) { 14 | throw new IllegalArgumentException("uri cannot be null"); 15 | } 16 | 17 | for(ImageURIHandler handler : handlers) { 18 | ImageHandle imageHandle = handler.handle(uri); 19 | 20 | if(imageHandle != null) { 21 | return imageHandle; 22 | } 23 | } 24 | 25 | throw new IllegalStateException("Unable to handle ImageURI: " + uri); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/image/ImageURIHandler.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.image; 2 | 3 | public interface ImageURIHandler { 4 | ImageHandle handle(ImageURI uri); 5 | } 6 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/image/ResourceImageHandle.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.image; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | public class ResourceImageHandle implements ImageHandle { 7 | private final Class cls; 8 | private final String name; 9 | 10 | public ResourceImageHandle(Class cls, String name) { 11 | this.cls = cls; 12 | this.name = name; 13 | } 14 | 15 | @Override 16 | public byte[] getImageData() throws IOException { 17 | try(InputStream stream = cls.getResourceAsStream(name)) { 18 | return stream.readAllBytes(); 19 | } 20 | } 21 | 22 | @Override 23 | public String getKey() { 24 | return "classpath:/" + cls.getName() + ":" + name; 25 | } 26 | 27 | @Override 28 | public boolean isFastSource() { 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/Properties.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx; 2 | 3 | import hs.mediasystem.util.javafx.property.SimpleReadOnlyObjectProperty; 4 | 5 | import javafx.beans.property.ObjectProperty; 6 | import javafx.beans.property.ReadOnlyProperty; 7 | 8 | public class Properties { 9 | public static ReadOnlyProperty readOnly(ObjectProperty property) { 10 | return new SimpleReadOnlyObjectProperty<>(property); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/action/Action.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.action; 2 | 3 | import javafx.beans.binding.StringExpression; 4 | import javafx.beans.property.ReadOnlyBooleanProperty; 5 | import javafx.event.Event; 6 | 7 | public interface Action { 8 | StringExpression titleProperty(); 9 | ReadOnlyBooleanProperty enabledProperty(); 10 | void trigger(Event event); 11 | } 12 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/base/FocusEvent.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.base; 2 | 3 | import javafx.event.Event; 4 | import javafx.event.EventType; 5 | 6 | public class FocusEvent extends Event { 7 | public static final EventType ANY = new EventType<>(EventType.ROOT, "FOCUS"); 8 | public static final EventType FOCUS_LOST = new EventType<>(ANY, "FOCUS_LOST"); 9 | public static final EventType FOCUS_GAINED = new EventType<>(ANY, "FOCUS_GAINED"); 10 | 11 | public FocusEvent(boolean focused) { 12 | super(focused ? FOCUS_GAINED : FOCUS_LOST); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/base/ItemSelectedEvent.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.base; 2 | 3 | import javafx.event.Event; 4 | import javafx.event.EventTarget; 5 | import javafx.event.EventType; 6 | 7 | public class ItemSelectedEvent extends Event { 8 | private final T item; 9 | 10 | public ItemSelectedEvent(EventTarget eventTarget, T item) { 11 | super(eventTarget, eventTarget, EventType.ROOT); 12 | 13 | this.item = item; 14 | } 15 | 16 | public T getItem() { 17 | return item; 18 | } 19 | } -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/control/Buttons.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.control; 2 | 3 | import hs.mediasystem.util.javafx.action.Action; 4 | 5 | import javafx.event.ActionEvent; 6 | import javafx.event.EventHandler; 7 | import javafx.scene.control.Button; 8 | 9 | public class Buttons { 10 | 11 | public static Button create(String styleClasses, String text, EventHandler eventHandler) { 12 | Button button = new Button(text); 13 | 14 | if(styleClasses != null) { 15 | button.getStyleClass().addAll(styleClasses.split(",(?: *)")); 16 | } 17 | 18 | button.setOnAction(eventHandler); 19 | 20 | return button; 21 | } 22 | 23 | public static Button create(String text, EventHandler eventHandler) { 24 | return create(null, text, eventHandler); 25 | } 26 | 27 | public static Button create(Action action) { 28 | Button button = new Button(); 29 | 30 | button.textProperty().bind(action.titleProperty()); 31 | button.disableProperty().bind(action.enabledProperty().map(b -> !b)); 32 | button.setOnAction(action::trigger); 33 | 34 | return button; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/control/GridPaneUtil.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.control; 2 | 3 | import javafx.scene.layout.ColumnConstraints; 4 | import javafx.scene.layout.RowConstraints; 5 | 6 | public class GridPaneUtil { 7 | public static void configure(javafx.scene.layout.GridPane gridPane, double[] columns, double[] rows) { 8 | for(double column : columns) { 9 | ColumnConstraints constraints = new ColumnConstraints(); 10 | 11 | constraints.setPercentWidth(column); 12 | 13 | gridPane.getColumnConstraints().add(constraints); 14 | } 15 | 16 | for(double row : rows) { 17 | RowConstraints constraints = new RowConstraints(); 18 | 19 | constraints.setPercentHeight(row); 20 | 21 | gridPane.getRowConstraints().add(constraints); 22 | } 23 | } 24 | 25 | public static GridPane create(double[] columns, double[] rows) { 26 | GridPane gridPane = new GridPane(); 27 | 28 | configure(gridPane, columns, rows); 29 | 30 | return gridPane; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/control/TablePane.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.control; 2 | 3 | import javafx.scene.Node; 4 | import javafx.scene.layout.GridPane; 5 | 6 | public class TablePane extends GridPane { 7 | private int row = 0; 8 | private int column = 0; 9 | 10 | public TablePane add(Node node) { 11 | getChildren().add(node); 12 | GridPane.setConstraints(node, column++, row); 13 | return this; 14 | } 15 | 16 | public TablePane add(Node node, int columnSpan) { 17 | getChildren().add(node); 18 | GridPane.setConstraints(node, column, row, columnSpan, 1); 19 | column += columnSpan; 20 | return this; 21 | } 22 | 23 | public TablePane nextColumn() { 24 | column++; 25 | return this; 26 | } 27 | 28 | public TablePane nextRow() { 29 | column = 0; 30 | row++; 31 | return this; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/property/SimpleReadOnlyObjectProperty.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.property; 2 | 3 | import javafx.beans.property.ObjectProperty; 4 | import javafx.beans.property.ReadOnlyObjectPropertyBase; 5 | 6 | public class SimpleReadOnlyObjectProperty extends ReadOnlyObjectPropertyBase { 7 | private final ObjectProperty property; 8 | 9 | public SimpleReadOnlyObjectProperty(ObjectProperty property) { 10 | this.property = property; 11 | this.property.addListener(obs -> fireValueChangedEvent()); 12 | } 13 | 14 | @Override 15 | public Object getBean() { 16 | return property.getBean(); 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return property.getName(); 22 | } 23 | 24 | @Override 25 | public T get() { 26 | return property.get(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/ui/carousel/CarouselListCell.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.ui.carousel; 2 | 3 | import javafx.beans.property.DoubleProperty; 4 | import javafx.beans.property.ObjectProperty; 5 | import javafx.beans.property.SimpleDoubleProperty; 6 | import javafx.beans.property.SimpleObjectProperty; 7 | import javafx.geometry.Orientation; 8 | import javafx.scene.control.ListCell; 9 | import javafx.scene.effect.Effect; 10 | 11 | public class CarouselListCell extends ListCell { 12 | private final DoubleProperty zoomProperty = new SimpleDoubleProperty(1.0); 13 | public DoubleProperty zoomProperty() { return zoomProperty; } 14 | 15 | private final ObjectProperty additionalEffectProperty = new SimpleObjectProperty<>(); 16 | public ObjectProperty additionalEffectProperty() { return additionalEffectProperty; } 17 | 18 | // TODO this is needed to get the CarouselListSkin to respect the ImageView's content bias... 19 | @Override 20 | public Orientation getContentBias() { 21 | return Orientation.HORIZONTAL; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/ui/carousel/LinearLayout.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.ui.carousel; 2 | 3 | import javafx.geometry.Dimension2D; 4 | import javafx.geometry.Orientation; 5 | 6 | public class LinearLayout extends Layout { 7 | 8 | @Override 9 | public CellIterator renderCellIterator(double fractionalIndex, Orientation orientation, Dimension2D size, int maxItems) { 10 | return new LinearCellIterator(this, fractionalIndex, orientation, size, maxItems); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/ui/csslayout/Resolvable.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.ui.csslayout; 2 | 3 | public interface Resolvable { 4 | 5 | String getNodeLayout(); 6 | boolean isResolved(); 7 | void setResolved(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/ui/csslayout/StylableHBox.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.ui.csslayout; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import javafx.css.CssMetaData; 7 | import javafx.css.Styleable; 8 | import javafx.css.StyleableProperty; 9 | import javafx.css.StyleablePropertyFactory; 10 | import javafx.scene.Node; 11 | import javafx.scene.layout.HBox; 12 | 13 | public class StylableHBox extends HBox implements Resolvable { 14 | private static final StyleablePropertyFactory FACTORY = new StyleablePropertyFactory<>(HBox.getClassCssMetaData()); 15 | 16 | private final StyleableProperty nodes = FACTORY.createStyleableStringProperty(this, "layout", "-fx-layout", s -> s.nodes); 17 | 18 | private boolean resolved; 19 | 20 | public StylableHBox(Node... nodes) { 21 | CssLayoutFactory.initialize(this, Arrays.asList(nodes)); 22 | } 23 | 24 | @Override 25 | public List> getCssMetaData() { 26 | return FACTORY.getCssMetaData(); 27 | } 28 | 29 | @Override 30 | public String getNodeLayout() { 31 | return nodes.getValue(); 32 | } 33 | 34 | @Override 35 | public boolean isResolved() { 36 | return resolved; 37 | } 38 | 39 | @Override 40 | public void setResolved() { 41 | resolved = true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/ui/csslayout/StylableVBox.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.ui.csslayout; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import javafx.css.CssMetaData; 7 | import javafx.css.Styleable; 8 | import javafx.css.StyleableProperty; 9 | import javafx.css.StyleablePropertyFactory; 10 | import javafx.scene.Node; 11 | import javafx.scene.layout.VBox; 12 | 13 | public class StylableVBox extends VBox implements Resolvable { 14 | private static final StyleablePropertyFactory FACTORY = new StyleablePropertyFactory<>(VBox.getClassCssMetaData()); 15 | 16 | private final StyleableProperty nodes = FACTORY.createStyleableStringProperty(this, "layout", "-fx-layout", s -> s.nodes); 17 | 18 | private boolean resolved; 19 | 20 | public StylableVBox(Node... nodes) { 21 | CssLayoutFactory.initialize(this, Arrays.asList(nodes)); 22 | } 23 | 24 | @Override 25 | public List> getCssMetaData() { 26 | return FACTORY.getCssMetaData(); 27 | } 28 | 29 | @Override 30 | public String getNodeLayout() { 31 | return nodes.getValue(); 32 | } 33 | 34 | @Override 35 | public boolean isResolved() { 36 | return resolved; 37 | } 38 | 39 | @Override 40 | public void setResolved() { 41 | resolved = true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/ui/gridlistviewskin/Group.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.ui.gridlistviewskin; 2 | 3 | public class Group { 4 | private final String title; 5 | private final int position; 6 | 7 | public Group(String title, int position) { 8 | this.title = title; 9 | this.position = position; 10 | } 11 | 12 | public String getTitle() { 13 | return title; 14 | } 15 | 16 | public int getPosition() { 17 | return position; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/ui/transition/StandardTransitions.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.ui.transition; 2 | 3 | import hs.mediasystem.util.javafx.ui.transition.domain.EffectList; 4 | import hs.mediasystem.util.javafx.ui.transition.domain.MultiNodeTransition; 5 | import hs.mediasystem.util.javafx.ui.transition.effects.Fade; 6 | import hs.mediasystem.util.javafx.ui.transition.multi.Custom; 7 | 8 | import javafx.util.Duration; 9 | 10 | public class StandardTransitions { 11 | 12 | public static final MultiNodeTransition fade(int millis, int delayMillis) { 13 | return new Custom(Duration.millis(delayMillis), new EffectList(Duration.millis(millis), new Fade())); 14 | } 15 | 16 | public static final MultiNodeTransition fade(int millis) { 17 | return new Custom(new EffectList(Duration.millis(millis), new Fade())); 18 | } 19 | 20 | public static final MultiNodeTransition fade() { 21 | return fade(500); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/ui/transition/domain/EffectList.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.ui.transition.domain; 2 | 3 | import java.util.List; 4 | 5 | import javafx.util.Duration; 6 | 7 | /** 8 | * A list of effects and their duration. 9 | */ 10 | public class EffectList { 11 | private final List effects; 12 | private final Duration duration; 13 | 14 | public EffectList(Duration duration, List effects) { 15 | this.duration = duration; 16 | this.effects = effects; 17 | } 18 | 19 | public EffectList(Duration duration, TransitionEffect effect) { 20 | this(duration, List.of(effect)); 21 | } 22 | 23 | public List getEffects() { 24 | return effects; 25 | } 26 | 27 | public Duration getDuration() { 28 | return duration; 29 | } 30 | } -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/javafx/ui/transition/domain/MultiNodeTransition.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.javafx.ui.transition.domain; 2 | 3 | import java.util.List; 4 | 5 | import javafx.scene.Node; 6 | 7 | public interface MultiNodeTransition { 8 | 9 | /** 10 | * Starts or restarts (if a new node was added) the transition. This method is 11 | * guaranteed to be called exactly once for each node added.

12 | * 13 | * The target node indicates which node should be displayed when all transitions 14 | * complete, or null if pane should be cleared. If not null 15 | * it is usually the most recently added node, but it may also be an existing node 16 | * (which is still part of the container) that was re-added before the transitions 17 | * completed. 18 | * 19 | * @param nodes the nodes to apply the transition over 20 | * @param targetNode the node that should be displayed once the transition completes, can be null if pane should be cleared 21 | * @param invert whether the animation direction should be inverted (only applies to animations that have a sense of direction) 22 | */ 23 | void restart(List nodes, Node targetNode, boolean invert); 24 | } 25 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/logging/DateTimeLoggingFormatter.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.logging; 2 | 3 | import java.text.SimpleDateFormat; 4 | 5 | public class DateTimeLoggingFormatter extends ClearLoggingFormatter { 6 | 7 | public DateTimeLoggingFormatter() { 8 | super(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/logging/FileHandler1.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.logging; 2 | 3 | import java.io.IOException; 4 | 5 | public class FileHandler1 extends PathCreatingFileHandler { 6 | 7 | public FileHandler1() throws IOException, SecurityException { 8 | super(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/logging/FileHandler2.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.logging; 2 | 3 | import java.io.IOException; 4 | 5 | public class FileHandler2 extends PathCreatingFileHandler { 6 | 7 | public FileHandler2() throws IOException, SecurityException { 8 | super(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/logging/TimeLoggingFormatter.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.logging; 2 | 3 | import java.text.SimpleDateFormat; 4 | 5 | public class TimeLoggingFormatter extends ClearLoggingFormatter { 6 | 7 | public TimeLoggingFormatter() { 8 | super(new SimpleDateFormat("HH:mm:ss.SSS")); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/natural/Levenshtein.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.natural; 2 | 3 | public class Levenshtein { 4 | 5 | public static double compare(final String s1, final String s2) { 6 | final int n = s1.length(); 7 | final int m = s2.length(); 8 | 9 | if(n == 0 || m == 0) { 10 | return 0; 11 | } 12 | 13 | return 1.0 - ((double)compare(s1, n, s2, m) / (Math.max(n, m))); 14 | } 15 | 16 | private static int compare(final String s1, final int n, final String s2, final int m) { 17 | int[][] matrix = new int[n + 1][m + 1]; 18 | 19 | for(int i = 0; i <= n; i++) { 20 | matrix[i][0] = i; 21 | } 22 | 23 | for(int i = 0; i <= m; i++) { 24 | matrix[0][i] = i; 25 | } 26 | 27 | for(int i = 1; i <= n; i++) { 28 | int s1i = s1.codePointAt(i - 1); 29 | 30 | for(int j = 1; j <= m; j++) { 31 | int s2j = s2.codePointAt(j - 1); 32 | final int cost = s1i == s2j ? 0 : 1; 33 | 34 | matrix[i][j] = min3(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost); 35 | } 36 | } 37 | 38 | return matrix[n][m]; 39 | } 40 | 41 | private static int min3(final int a, final int b, final int c) { 42 | return Math.min(Math.min(a, b), c); 43 | } 44 | } -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/parser/CssStyle.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.parser; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public enum CssStyle implements Type { 6 | IDENTIFIER("[_A-Za-z](?:[_A-Za-z0-9-]*[_A-Za-z0-9])?"), // Identifiers consist of letters, digits, underscores and hyphens, but cannot start with a digit or hypen and cannot end with a hyphen 7 | NUMBER("-?[0-9]+(?:\\\\.[0-9]+)?"), 8 | STRING("\"(?:\\\\.|[^\\\\\"])*\""), 9 | OPERATOR("\\(|\\)|\\{|\\}|[^A-Za-z0-9 ]+"); 10 | 11 | private final Pattern pattern; 12 | 13 | CssStyle(String pattern) { 14 | this.pattern = Pattern.compile(pattern); 15 | } 16 | 17 | @Override 18 | public Pattern getPattern() { 19 | return pattern; 20 | } 21 | } -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/parser/JavaStyle.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.parser; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public enum JavaStyle implements Type { 6 | IDENTIFIER("[$_A-Za-z][$_A-Za-z0-9]*"), // Identifiers consist of letters, digits, underscores and dollar signs, but cannot start with a digit 7 | NUMBER("-?[0-9]+(?:\\\\.[0-9]+)?"), 8 | STRING("\"(?:\\\\.|[^\\\\\"])*\""), 9 | OPERATOR("\\(|\\)|\\{|\\}|[^A-Za-z0-9 ]+"); 10 | 11 | private final Pattern pattern; 12 | 13 | JavaStyle(String pattern) { 14 | this.pattern = Pattern.compile(pattern); 15 | } 16 | 17 | @Override 18 | public Pattern getPattern() { 19 | return pattern; 20 | } 21 | } -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/parser/Token.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.parser; 2 | 3 | public class Token { 4 | private final Type type; 5 | private final String text; 6 | private final int offset; 7 | private final String seperator; 8 | 9 | public Token(Type type, String text, String seperator, int offset) { 10 | this.type = type; 11 | this.text = text; 12 | this.seperator = seperator; 13 | this.offset = offset; 14 | } 15 | 16 | public String getText() { 17 | return text; 18 | } 19 | 20 | public String getSeperator() { 21 | return seperator; 22 | } 23 | 24 | public int getOffset() { 25 | return offset; 26 | } 27 | 28 | public int getEndOffset() { 29 | return offset + (text == null ? 0 : text.length()); 30 | } 31 | 32 | public boolean isEnd() { 33 | return type == null; 34 | } 35 | 36 | public boolean matches(Type type, String value) { 37 | return this.type == type && value.equals(text); 38 | } 39 | 40 | public Type getType() { 41 | return type; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return text == null ? "END_OF_INPUT" : "\"" + text + "\""; 47 | } 48 | } -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/parser/Type.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.parser; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public interface Type { 6 | Pattern getPattern(); 7 | } 8 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/time/SimulatedTimeSource.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.time; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | public class SimulatedTimeSource implements TimeSource { 7 | private Instant currentTime; 8 | 9 | public SimulatedTimeSource(Instant initialTime) { 10 | this.currentTime = initialTime; 11 | } 12 | 13 | @Override 14 | public synchronized void sleep(Duration duration) throws InterruptedException { 15 | Instant wakeUpTime = currentTime.plus(duration); 16 | 17 | while(wakeUpTime.isAfter(currentTime)) { 18 | wait(); 19 | } 20 | } 21 | 22 | public synchronized void advanceTime(Duration duration) { 23 | currentTime = currentTime.plus(duration); 24 | // Notify all waiting threads 25 | notifyAll(); 26 | } 27 | 28 | public synchronized void advanceTime(long milliseconds) { 29 | advanceTime(Duration.ofMillis(milliseconds)); 30 | } 31 | 32 | @Override 33 | public Instant instant() { 34 | return currentTime; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/time/SystemTimeSource.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.time; 2 | 3 | import java.time.Duration; 4 | import java.time.Instant; 5 | 6 | class SystemTimeSource implements TimeSource { 7 | static final TimeSource INSTANCE = new SystemTimeSource(); 8 | 9 | @Override 10 | public void sleep(Duration duration) throws InterruptedException { 11 | Thread.sleep(duration); 12 | } 13 | 14 | @Override 15 | public Instant instant() { 16 | return Instant.now(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mediasystem-util/src/main/java/hs/mediasystem/util/time/TimeSource.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util.time; 2 | 3 | import java.time.Duration; 4 | import java.time.InstantSource; 5 | 6 | public interface TimeSource extends InstantSource { 7 | 8 | static TimeSource system() { 9 | return SystemTimeSource.INSTANCE; 10 | } 11 | 12 | void sleep(Duration duration) throws InterruptedException; 13 | } 14 | -------------------------------------------------------------------------------- /mediasystem-util/src/test/java/hs/mediasystem/util/ArchitectureTest.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util; 2 | 3 | import com.tngtech.archunit.core.importer.ImportOption; 4 | import com.tngtech.archunit.junit.AnalyzeClasses; 5 | import com.tngtech.archunit.junit.ArchTest; 6 | import com.tngtech.archunit.lang.ArchRule; 7 | import com.tngtech.archunit.library.DependencyRules; 8 | 9 | import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; 10 | 11 | @AnalyzeClasses(packages = ArchitectureTest.BASE_PACKAGE_NAME, importOptions = ImportOption.DoNotIncludeTests.class) 12 | public class ArchitectureTest { 13 | static final String BASE_PACKAGE_NAME = "hs.mediasystem.util"; 14 | 15 | @ArchTest 16 | private final ArchRule packagesShouldBeFreeOfCycles = slices().matching("(**)").should().beFreeOfCycles(); 17 | 18 | @ArchTest 19 | private final ArchRule noClassesShouldDependOnUpperPackages = DependencyRules.NO_CLASSES_SHOULD_DEPEND_UPPER_PACKAGES; 20 | } 21 | -------------------------------------------------------------------------------- /mediasystem-util/src/test/java/hs/mediasystem/util/PostConstructCaller.java: -------------------------------------------------------------------------------- 1 | package hs.mediasystem.util; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.lang.reflect.Method; 5 | 6 | import javax.annotation.PostConstruct; 7 | 8 | public class PostConstructCaller { 9 | 10 | public static void call(Object obj) { 11 | for(Method method : obj.getClass().getDeclaredMethods()) { 12 | if(method.getAnnotation(PostConstruct.class) != null) { 13 | try { 14 | method.setAccessible(true); 15 | method.invoke(obj); 16 | } 17 | catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 18 | throw new IllegalStateException(e); 19 | } 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /screenshot-1.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7ca0efe78bb3d33be6df479673bed3b7598162e2c9421aa110f7276b4d2f2ec5 3 | size 63220 4 | -------------------------------------------------------------------------------- /screenshot-2.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:beb1393253ed9991628fcea2f514a608ca63b718164cb26df5ef4bdd9f57c0af 3 | size 57859 4 | -------------------------------------------------------------------------------- /screenshot-3.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:88daf263ffbb859d2cd020e7c17439a3e79772f289fd8598959ccb9d4b3bd477 3 | size 39033 4 | -------------------------------------------------------------------------------- /screenshot-4.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:36e902e3ec415a08748e6e9869ad14eb8f23d052b62618f4c2e7bda337bae5f2 3 | size 46133 4 | --------------------------------------------------------------------------------