├── .blueprint ├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── Console │ └── Commands │ │ ├── CompareVersion.php │ │ ├── DisableMfa.php │ │ ├── FlushFfmpegProcessCache.php │ │ ├── FlushJobsTable.php │ │ ├── GenerateAppKey.php │ │ ├── PruneStaleHlsProcesses.php │ │ ├── RefreshEpg.php │ │ ├── RefreshPlaylist.php │ │ ├── ResetPassword.php │ │ ├── RestartQueue.php │ │ ├── ShowConfig.php │ │ ├── SqliteWalEnable.php │ │ ├── TestBroadcasting.php │ │ ├── TestXtream.php │ │ └── Xtream2Strm.php ├── Enums │ ├── ChannelLogoType.php │ ├── PlaylistChannelId.php │ └── Status.php ├── Events │ ├── CustomPlaylistCreated.php │ ├── EpgCreated.php │ ├── EpgDeleted.php │ ├── EpgUpdated.php │ ├── MergedPlaylistCreated.php │ ├── PlaylistCreated.php │ ├── PlaylistDeleted.php │ ├── PlaylistUpdated.php │ └── SyncCompleted.php ├── Exceptions │ └── SourceNotResponding.php ├── Facades │ ├── PlaylistUrlFacade.php │ └── ProxyFacade.php ├── Filament │ ├── Auth │ │ ├── EditProfile.php │ │ └── Login.php │ ├── Exports │ │ └── ChannelExporter.php │ ├── Imports │ │ └── ChannelImporter.php │ ├── Pages │ │ ├── Backups.php │ │ ├── CustomDashboard.php │ │ ├── Preferences.php │ │ └── StreamingChannelStats.php │ ├── Resources │ │ ├── ChannelResource.php │ │ ├── ChannelResource │ │ │ └── Pages │ │ │ │ ├── CreateChannel.php │ │ │ │ ├── EditChannel.php │ │ │ │ ├── ListChannels.php │ │ │ │ └── ViewChannel.php │ │ ├── CustomPlaylistResource.php │ │ ├── CustomPlaylistResource │ │ │ ├── Pages │ │ │ │ ├── CreateCustomPlaylist.php │ │ │ │ ├── EditCustomPlaylist.php │ │ │ │ └── ListCustomPlaylists.php │ │ │ └── RelationManagers │ │ │ │ ├── ChannelsRelationManager.php │ │ │ │ └── TagsRelationManager.php │ │ ├── EpgChannelResource.php │ │ ├── EpgChannelResource │ │ │ └── Pages │ │ │ │ ├── CreateEpgChannel.php │ │ │ │ ├── EditEpgChannel.php │ │ │ │ └── ListEpgChannels.php │ │ ├── EpgMapResource.php │ │ ├── EpgMapResource │ │ │ └── Pages │ │ │ │ ├── CreateEpgMap.php │ │ │ │ ├── EditEpgMap.php │ │ │ │ └── ListEpgMaps.php │ │ ├── EpgResource.php │ │ ├── EpgResource │ │ │ └── Pages │ │ │ │ ├── CreateEpg.php │ │ │ │ ├── EditEpg.php │ │ │ │ └── ListEpgs.php │ │ ├── GroupResource.php │ │ ├── GroupResource │ │ │ ├── Pages │ │ │ │ ├── CreateGroup.php │ │ │ │ ├── EditGroup.php │ │ │ │ ├── ListGroups.php │ │ │ │ └── ViewGroup.php │ │ │ └── RelationManagers │ │ │ │ └── ChannelsRelationManager.php │ │ ├── MergedPlaylistResource.php │ │ ├── MergedPlaylistResource │ │ │ ├── Pages │ │ │ │ ├── CreateMergedPlaylist.php │ │ │ │ ├── EditMergedPlaylist.php │ │ │ │ └── ListMergedPlaylists.php │ │ │ └── RelationManagers │ │ │ │ └── PlaylistsRelationManager.php │ │ ├── PlaylistAuthResource.php │ │ ├── PlaylistAuthResource │ │ │ ├── Pages │ │ │ │ ├── CreatePlaylistAuth.php │ │ │ │ ├── EditPlaylistAuth.php │ │ │ │ └── ListPlaylistAuths.php │ │ │ └── RelationManagers │ │ │ │ └── PlaylistsRelationManager.php │ │ ├── PlaylistResource.php │ │ ├── PlaylistResource │ │ │ └── Pages │ │ │ │ ├── CreatePlaylist.php │ │ │ │ ├── EditPlaylist.php │ │ │ │ └── ListPlaylists.php │ │ ├── PlaylistSyncStatusResource.php │ │ ├── PlaylistSyncStatusResource │ │ │ ├── Pages │ │ │ │ ├── CreatePlaylistSyncStatus.php │ │ │ │ ├── EditPlaylistSyncStatus.php │ │ │ │ ├── ListPlaylistSyncStatuses.php │ │ │ │ └── ViewPlaylistSyncStatus.php │ │ │ └── RelationManagers │ │ │ │ └── LogsRelationManager.php │ │ ├── PostProcessResource.php │ │ ├── PostProcessResource │ │ │ ├── Pages │ │ │ │ ├── CreatePostProcess.php │ │ │ │ ├── EditPostProcess.php │ │ │ │ └── ListPostProcesses.php │ │ │ └── RelationManagers │ │ │ │ ├── LogsRelationManager.php │ │ │ │ └── ProcessesRelationManager.php │ │ ├── SeriesResource.php │ │ └── SeriesResource │ │ │ ├── Pages │ │ │ ├── CreateSeries.php │ │ │ ├── EditSeries.php │ │ │ └── ListSeries.php │ │ │ └── RelationManagers │ │ │ └── EpisodesRelationManager.php │ └── Widgets │ │ ├── DiscordWidget.php │ │ ├── DocumentsWidget.php │ │ ├── DonateCrypto.php │ │ ├── KoFiWidget.php │ │ ├── PayPalDonateWidget.php │ │ ├── StatsOverview.php │ │ └── UpdateNoticeWidget.php ├── Forms │ └── Components │ │ ├── MediaFlowProxyUrl.php │ │ ├── PlaylistEpgUrl.php │ │ └── PlaylistM3uUrl.php ├── Http │ └── Controllers │ │ ├── Controller.php │ │ ├── EpgController.php │ │ ├── EpgFileController.php │ │ ├── EpgGenerateController.php │ │ ├── HlsStreamController.php │ │ ├── PlaylistController.php │ │ ├── PlaylistGenerateController.php │ │ ├── StreamController.php │ │ ├── UserController.php │ │ └── WebhookTestController.php ├── Infolists │ └── Components │ │ ├── SeriesPreview.php │ │ └── VideoPreview.php ├── Jobs │ ├── ChannelFindAndReplace.php │ ├── ChannelFindAndReplaceReset.php │ ├── CopyPlaylist.php │ ├── CreateBackup.php │ ├── DuplicatePlaylist.php │ ├── MapEpgToChannels.php │ ├── MapEpgToChannelsComplete.php │ ├── MapPlaylistChannelsToEpg.php │ ├── ProcessEpgImport.php │ ├── ProcessEpgImportChunk.php │ ├── ProcessEpgImportComplete.php │ ├── ProcessM3uImport.php │ ├── ProcessM3uImportChunk.php │ ├── ProcessM3uImportComplete.php │ ├── ProcessM3uImportSeries.php │ ├── ProcessM3uImportSeriesComplete.php │ ├── ProcessM3uImportSeriesEpisodes.php │ ├── RestartQueue.php │ ├── RestoreBackup.php │ ├── RunPostProcess.php │ ├── SyncSeriesStrmFiles.php │ └── SyncXtreamSeries.php ├── Listeners │ ├── BackupFailed.php │ ├── CustomPlaylistListener.php │ ├── EpgListener.php │ ├── GroupListener.php │ ├── MergedPlaylistListener.php │ ├── PlaylistListener.php │ └── SyncListener.php ├── Livewire │ ├── ChannelStreamStats.php │ └── ProfileComponent.php ├── Models │ ├── Category.php │ ├── Channel.php │ ├── ChannelFailover.php │ ├── CustomPlaylist.php │ ├── Epg.php │ ├── EpgChannel.php │ ├── EpgMap.php │ ├── Episode.php │ ├── Group.php │ ├── Job.php │ ├── MergedPlaylist.php │ ├── Playlist.php │ ├── PlaylistAuth.php │ ├── PlaylistSyncStatus.php │ ├── PlaylistSyncStatusLog.php │ ├── PostProcess.php │ ├── PostProcessLog.php │ ├── Season.php │ ├── Series.php │ ├── SourceGroup.php │ └── User.php ├── Pivots │ ├── CustomPlaylistPivot.php │ ├── MergedPlaylistPivot.php │ ├── PlaylistAuthPivot.php │ └── PostProcessPivot.php ├── Policies │ ├── CustomPlaylistPolicy.php │ ├── EpgPolicy.php │ ├── MergedPlaylistPolicy.php │ └── PlaylistPolicy.php ├── Providers │ ├── AppServiceProvider.php │ ├── Filament │ │ └── AdminPanelProvider.php │ ├── HorizonServiceProvider.php │ └── VersionServiceProvider.php ├── Rules │ ├── CheckIfUrlOrLocalPath.php │ └── Cron.php ├── Services │ ├── FfmpegCodecService.php │ ├── HlsStreamService.php │ ├── PlaylistUrlService.php │ ├── ProxyService.php │ ├── SimilaritySearchService.php │ └── XtreamService.php ├── Settings │ └── GeneralSettings.php ├── Tables │ └── Columns │ │ ├── PivotNameColumn.php │ │ └── PlaylistUrlColumn.php └── Traits │ ├── HasParentResource.php │ ├── Schedulable.php │ ├── ShortUrlTrait.php │ └── TracksActiveStreams.php ├── artisan ├── bootstrap ├── app.php ├── cache │ └── .gitignore └── providers.php ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── backup-restore.php ├── backup.php ├── blueprint.php ├── broadcasting.php ├── cache.php ├── cors.php ├── database.php ├── dev.php ├── filament.php ├── filesystems.php ├── horizon.php ├── livewire.php ├── log-viewer.php ├── logging.php ├── mail.php ├── proxy.php ├── queue.php ├── reverb.php ├── sanctum.php ├── scramble.php ├── services.php ├── session.php ├── settings.php ├── short-url.php └── xtream.php ├── database ├── .gitignore ├── factories │ ├── CategoryFactory.php │ ├── ChannelFactory.php │ ├── ChannelFailoverFactory.php │ ├── CustomPlaylistFactory.php │ ├── EpgChannelFactory.php │ ├── EpgFactory.php │ ├── EpgMapFactory.php │ ├── EpisodeFactory.php │ ├── GroupFactory.php │ ├── MergedPlaylistFactory.php │ ├── PlaylistAuthFactory.php │ ├── PlaylistFactory.php │ ├── PlaylistSyncStatusFactory.php │ ├── PlaylistSyncStatusLogFactory.php │ ├── PostProcessFactory.php │ ├── SeasonFactory.php │ ├── SeriesFactory.php │ └── UserFactory.php ├── migrations │ ├── 0001_01_01_000000_create_users_table.php │ ├── 0001_01_01_000001_create_cache_table.php │ ├── 0001_01_01_000002_create_jobs_table.php │ ├── 2019_12_22_015115_create_short_urls_table.php │ ├── 2019_12_22_015214_create_short_url_visits_table.php │ ├── 2020_02_11_224848_update_short_url_table_for_version_two_zero_zero.php │ ├── 2020_02_12_008432_update_short_url_visits_table_for_version_two_zero_zero.php │ ├── 2020_04_10_224546_update_short_url_table_for_version_three_zero_zero.php │ ├── 2020_04_20_009283_update_short_url_table_add_option_to_forward_query_params.php │ ├── 2022_12_14_083707_create_settings_table.php │ ├── 2024_12_17_203030_create_admin_user.php │ ├── 2024_12_18_233316_create_notifications_table.php │ ├── 2024_12_19_034846_create_imports_table.php │ ├── 2024_12_19_034847_create_exports_table.php │ ├── 2024_12_19_034848_create_failed_import_rows_table.php │ ├── 2024_12_19_154247_create_playlists_table.php │ ├── 2024_12_19_154248_create_groups_table.php │ ├── 2024_12_19_154249_create_channels_table.php │ ├── 2025_01_06_161153_add_import_batch_no_to_channels.php │ ├── 2025_01_19_220431_add_import_batch_no_to_groups.php │ ├── 2025_01_21_164226_create_epgs_table.php │ ├── 2025_01_21_164227_create_epg_channels_table.php │ ├── 2025_01_21_170519_add_synced_column_to_epgs.php │ ├── 2025_01_21_170900_add_import_batch_to_epg_channels.php │ ├── 2025_01_21_171508_add_status_to_epg.php │ ├── 2025_01_21_171725_add_errors_columnd_to_epg.php │ ├── 2025_01_21_173519_add_uploads_column_to_epg.php │ ├── 2025_01_21_174319_add_user_id_to_epg_channels.php │ ├── 2025_01_21_224113_add_icon_to_epg_channels.php │ ├── 2025_01_22_200353_create_epg_programmes_table.php │ ├── 2025_01_22_200738_remove_programme_column_from_epg_channels.php │ ├── 2025_01_23_204711_add_sync_time_to_playlists.php │ ├── 2025_01_28_152132_create_merged_playlists_table.php │ ├── 2025_01_28_152133_create_merged_playlist_playlist_table.php │ ├── 2025_01_28_152137_create_custom_playlists_table.php │ ├── 2025_01_28_152138_create_channel_custom_playlist_table.php │ ├── 2025_01_28_225053_add_sync_time_to_epgs.php │ ├── 2025_01_28_225829_add_index_to_epg_channels.php │ ├── 2025_01_29_190403_add_epg_channel_id_column_to_channels.php │ ├── 2025_02_01_151321_drop_epg_programmes_table.php │ ├── 2025_02_01_153803_add_uuid_column_to_epgs.php │ ├── 2025_02_06_161829_add_auto_sync_toggle_to_playlists.php │ ├── 2025_02_06_161839_add_auto_sync_toggle_to_epgs.php │ ├── 2025_02_09_165446_add_avatar_url_to_users.php │ ├── 2025_02_09_195223_create_filament-jobs-monitor_table.php │ ├── 2025_02_09_215228_add_progress_column_to_playlists.php │ ├── 2025_02_09_215233_add_progress_column_to_playlists.php │ ├── 2025_02_13_171533_add_processing_flag_to_playlists.php │ ├── 2025_02_13_171537_add_processing_flag_to_epgs.php │ ├── 2025_02_13_215803_create_jobs_table.php │ ├── 2025_02_13_224233_add_sync_interval_to_playlists.php │ ├── 2025_02_13_224236_add_sync_interval_to_epgs.php │ ├── 2025_02_14_214426_add_playlist_import_prefs_column_to_playlists.php │ ├── 2025_02_14_222330_add_groups_column_to_playlists.php │ ├── 2025_02_15_150422_reset_playlist_interval_on_playlists.php │ ├── 2025_02_15_150833_reset_playlist_interval_on_epgs.php │ ├── 2025_02_16_181719_create_epg_maps_table.php │ ├── 2025_02_16_184838_add_counts_to_epg_maps.php │ ├── 2025_02_16_193535_add_override_column_to_epg_maps.php │ ├── 2025_02_18_173424_add_enable_toggle_to_playlists.php │ ├── 2025_02_18_185618_add_internal_group_name_to_groups.php │ ├── 2025_02_19_152211_add_is_custom_column_to_groups.php │ ├── 2025_02_21_155914_drop_queue_monitors_table.php │ ├── 2025_02_21_205916_add_uploads_column_to_playlists.php │ ├── 2025_02_26_200928_add_xtream_columns_to_playlists.php │ ├── 2025_02_27_221823_add_user_agent_ssl_prefs_to_playlists.php │ ├── 2025_02_27_221833_add_user_agent_ssl_prefs_to_epgs.php │ ├── 2025_02_27_224518_add_default_user_agent_to_playlists_and_epgs.php │ ├── 2025_03_04_140444_add_editable_attributes_to_channels.php │ ├── 2025_03_04_145644_add_icon_preference_toggle_to_channels.php │ ├── 2025_03_05_142409_add_recurring_toggle_to_epg_maps.php │ ├── 2025_03_05_145318_add_playlist_id_column_to_epg_maps.php │ ├── 2025_03_05_165633_add_mapped_at_column_to_epg_maps.php │ ├── 2025_03_06_155450_add_custom_id_and_url_to_channels.php │ ├── 2025_03_07_153936_add_sort_column_to_channels.php │ ├── 2025_03_07_190744_add_auto_channel_output_option_to_playlists.php │ ├── 2025_03_09_161832_add_xtream_status_to_playlists.php │ ├── 2025_03_10_205435_add_extvlcopt_column_to_channels.php │ ├── 2025_03_12_215809_create_personal_access_tokens_table.php │ ├── 2025_03_12_220659_create_breezy_sessions_table.php │ ├── 2025_03_18_132548_add_auto_sort_order_to_playlists.php │ ├── 2025_03_19_161419_add_enable_proxy_to_playlists.php │ ├── 2025_03_19_161433_add_enable_proxy_to_custom_playlists.php │ ├── 2025_03_19_161447_add_enable_proxy_to_merged_playlists.php │ ├── 2025_03_20_131924_add_hdhr_tuners_to_playlists.php │ ├── 2025_03_20_174644_add_kodi_drop_column_to_channels.php │ ├── 2025_03_20_230059_add_channel_id_selection_to_playlists.php │ ├── 2025_03_25_204141_add_preferred_local_to_epg.php │ ├── 2025_03_31_212827_create_playlist_auths_table.php │ ├── 2025_03_31_221549_add_authable_pivot_table.php │ ├── 2025_04_03_140401_add_dummy_epg_config_to_playlists.php │ ├── 2025_04_15_212653_add_short_url_columns_to_playlists.php │ ├── 2025_04_19_175837_create_tag_tables.php │ ├── 2025_04_21_203422_create_post_processes_table.php │ ├── 2025_04_21_203508_create_processables_table.php │ ├── 2025_04_21_213721_crate_post_processing_logs_table.php │ ├── 2025_04_24_205934_add_new_column_to_channels.php │ ├── 2025_04_24_205937_add_new_column_to_groups.php │ ├── 2025_04_24_212113_create_playlist_sync_statuses_table.php │ ├── 2025_04_24_214454_add_stats_meta_to_playlist_sync_status.php │ ├── 2025_04_26_124521_add_dummy_epg_category_to_playlists.php │ ├── 2025_04_28_170814_create_playlist_sync_status_logs_table.php │ ├── 2025_04_28_170938_remove_and_move_sync_status_to_log_entries.php │ ├── 2025_05_03_172748_add_user_agent_to_custom_merged_playlists.php │ ├── 2025_05_05_180136_add_catchup_to_channels.php │ ├── 2025_05_07_202251_remove_batch_job_tables.php │ ├── 2025_05_09_145446_alter_notifications_data_to_jsonb.php │ ├── 2025_05_09_220958_update_epg_channel_title_column.php │ ├── 2025_05_20_135723_convert_json_to_jsonb.php │ ├── 2025_05_21_185308_create_categories_table.php │ ├── 2025_05_21_185309_create_series_table.php │ ├── 2025_05_21_185310_create_seasons_table.php │ ├── 2025_05_21_185311_create_episodes_table.php │ ├── 2025_05_21_185917_add_series_progress_to_playlists.php │ ├── 2025_05_21_192755_add_source_series_id_to_series.php │ ├── 2025_05_21_213847_update_date_time_column.php │ ├── 2025_05_22_114818_add_sort_order_to_series.php │ ├── 2025_05_22_201214_add_sync_location_to_series.php │ ├── 2025_05_24_124551_add_playlist_source_groups_table.php │ ├── 2025_05_25_135318_live_enabled_by_default.php │ ├── 2025_05_27_205110_add_proxy_options_to_playlists.php │ ├── 2025_05_30_131848_create_channel_failovers_table.php │ ├── 2025_05_30_152649_add_available_streams_to_playlists.php │ └── 2025_06_01_020531_add_sort_order_to_groups_table.php ├── seeders │ └── DatabaseSeeder.php └── settings │ ├── 2025_04_21_140953_create_general_settings.php │ ├── 2025_05_01_160604_add_mfp_user_agent_fields.php │ ├── 2025_05_16_070340_add_ffmpeg_codec_fields.php │ ├── 2025_05_19_130544_add_ffmpeg_path_select_option.php │ ├── 2025_05_29_152250_add_hw_accell_ffmpeg_settings.php │ ├── 2025_05_29_180906_add_ffmpeg_custom_command_template_settings.php │ └── 2025_06_03_190045_add_ffmpeg_hls_settings.php ├── docker ├── 8.4 │ ├── my.cnf │ ├── nginx │ │ ├── laravel.conf │ │ └── nginx.conf │ ├── php.ini │ ├── redis.conf │ ├── supervisord.conf │ └── www.conf ├── mariadb │ └── create-testing-database.sh ├── mysql │ └── create-testing-database.sh └── pgsql │ └── create-testing-database.sql ├── draft.yml ├── package-lock.json ├── package.json ├── phpunit.xml ├── postcss.config.js ├── public ├── .htaccess ├── css │ ├── aymanalhattami │ │ └── filament-slim-scrollbar │ │ │ └── filament-slim-scrollbar.css │ ├── devonab │ │ └── filament-easy-footer │ │ │ └── filament-easy-footer-styles.css │ ├── filament │ │ ├── filament │ │ │ └── app.css │ │ ├── forms │ │ │ └── forms.css │ │ └── support │ │ │ └── support.css │ └── ryanchandler │ │ └── filament-progress-column │ │ └── filament-progress-column.css ├── favicon.ico ├── favicon.png ├── favicon.svg ├── images │ ├── bmc-button.svg │ ├── bmm-logo.svg │ ├── crypto-icons │ │ ├── bitcoin.svg │ │ ├── crypto-coins.svg │ │ ├── ethereum.svg │ │ ├── litecoin.svg │ │ ├── ripple.svg │ │ ├── solana.svg │ │ └── tether.svg │ ├── discord.svg │ ├── github.svg │ ├── plex-logo.svg │ └── xtream-codes.png ├── index.php ├── js │ ├── devonab │ │ └── filament-easy-footer │ │ │ └── filament-easy-footer-scripts.js │ └── filament │ │ ├── filament │ │ ├── app.js │ │ └── echo.js │ │ ├── forms │ │ └── components │ │ │ ├── color-picker.js │ │ │ ├── date-time-picker.js │ │ │ ├── file-upload.js │ │ │ ├── key-value.js │ │ │ ├── markdown-editor.js │ │ │ ├── rich-editor.js │ │ │ ├── select.js │ │ │ ├── tags-input.js │ │ │ └── textarea.js │ │ ├── notifications │ │ └── notifications.js │ │ ├── support │ │ ├── async-alpine.js │ │ └── support.js │ │ ├── tables │ │ └── components │ │ │ └── table.js │ │ └── widgets │ │ └── components │ │ ├── chart.js │ │ └── stats-overview │ │ └── stat │ │ └── chart.js ├── logo.png ├── logo.svg ├── placeholder.png ├── robots.txt └── vendor │ └── log-viewer │ ├── app.css │ ├── app.js │ ├── app.js.LICENSE.txt │ ├── img │ ├── log-viewer-128.png │ ├── log-viewer-32.png │ └── log-viewer-64.png │ └── mix-manifest.json ├── rebuild.sh ├── refresh.sh ├── resources ├── css │ ├── app.css │ └── components │ │ ├── footer.css │ │ ├── tables.scss │ │ └── widgets.css ├── js │ ├── app.js │ ├── echo.js │ └── vendor │ │ ├── qrcode.js │ │ └── video.js └── views │ ├── components │ └── qr-modal.blade.php │ ├── errors │ ├── 404.blade.php │ └── 503.blade.php │ ├── filament │ ├── admin │ │ └── logo.blade.php │ ├── pages │ │ ├── dashboard.blade.php │ │ ├── stats.blade.php │ │ └── streaming-channel-stats.blade.php │ └── widgets │ │ ├── discord-widget.blade.php │ │ ├── docs-widget.blade.php │ │ ├── donate-crypto.blade.php │ │ ├── ko-fi-widget.blade.php │ │ ├── pay-pal-donate-widget.blade.php │ │ └── update-notice-widget.blade.php │ ├── footer.blade.php │ ├── forms │ └── components │ │ ├── media-flow-proxy-url.blade.php │ │ ├── playlist-epg-url.blade.php │ │ └── playlist-m3u-url.blade.php │ ├── hdhr.blade.php │ ├── infolists │ └── components │ │ ├── series-preview.blade.php │ │ └── video-preview.blade.php │ ├── livewire │ └── channel-stream-stats.blade.php │ ├── modals │ └── hardware-accel-info.blade.php │ ├── tables │ └── columns │ │ ├── playlist-auth-name.blade.php │ │ └── playlist-auth-url.blade.php │ └── vendor │ ├── filament-breezy │ └── components │ │ └── grid-section.blade.php │ ├── filament-progress-column │ └── column.blade.php │ └── log-viewer │ ├── .gitkeep │ └── index.blade.php ├── routes ├── api.php ├── channels.php ├── console.php └── web.php ├── screenshots ├── channel-management.jpg ├── dashboard-light.jpg ├── dashboard.jpg ├── group-detail.jpg ├── group-management.jpg ├── playlist-manager-popout.jpg └── playlist-manager.jpg ├── start-container ├── storage ├── app │ ├── .gitignore │ ├── private │ │ └── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── stubs └── blueprint │ ├── constructor.stub │ ├── controller.authorize-resource.stub │ ├── controller.class.stub │ ├── controller.method.stub │ ├── draft.stub │ ├── event.stub │ ├── factory.stub │ ├── job.stub │ ├── mail.stub │ ├── mail.view.stub │ ├── migration.stub │ ├── model.casts.stub │ ├── model.class.stub │ ├── model.connection.stub │ ├── model.fillable.stub │ ├── model.guarded.stub │ ├── model.hidden.stub │ ├── model.incrementing.stub │ ├── model.method.comment.stub │ ├── model.method.stub │ ├── model.table.stub │ ├── model.timestamps.stub │ ├── notification.stub │ ├── pest.test.case.stub │ ├── pest.test.class.stub │ ├── phpunit.test.case.stub │ ├── phpunit.test.class.stub │ ├── policy.class.stub │ ├── policy.method.create.stub │ ├── policy.method.delete.stub │ ├── policy.method.forceDelete.stub │ ├── policy.method.restore.stub │ ├── policy.method.update.stub │ ├── policy.method.view.stub │ ├── policy.method.viewAny.stub │ ├── request.stub │ ├── resource.stub │ ├── seeder.stub │ └── view.stub ├── tailwind.config.js ├── tests ├── Feature │ └── ExampleTest.php ├── Pest.php ├── TestCase.php └── Unit │ ├── ExampleTest.php │ └── Jobs │ └── ProcessM3uImportTest.php └── vite.config.js /.blueprint: -------------------------------------------------------------------------------- 1 | created: 2 | - database/factories/ChannelFailoverFactory.php 3 | - database/migrations/2025_05_30_131848_create_channel_failovers_table.php 4 | - app/Models/ChannelFailover.php 5 | models: 6 | ChannelFailover: { user_id: 'id foreign', channel_id: 'id foreign', channel_failover_id: 'id foreign:channels', sort: 'unsigned integer', metadata: jsonb } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [docker-compose.yml] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.blade.php diff=html 4 | *.css diff=css 5 | *.html diff=html 6 | *.md diff=markdown 7 | *.php diff=php 8 | 9 | /.github export-ignore 10 | CHANGELOG.md export-ignore 11 | .styleci.yml export-ignore 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /storage/pail 8 | /vendor 9 | /docker/sail-mariadb 10 | docker-config/ 11 | .env 12 | .env.backup 13 | .env.production 14 | .phpactor.json 15 | .phpunit.result.cache 16 | Homestead.json 17 | Homestead.yaml 18 | auth.json 19 | npm-debug.log 20 | yarn-error.log 21 | /.fleet 22 | /.idea 23 | /.nova 24 | /.vscode 25 | /.zed 26 | *.sqlite 27 | *.sql 28 | *.csv 29 | *.m3u 30 | docker-compose.yml 31 | DockerfileDev 32 | .rr.yaml 33 | rr 34 | websockets.sh -------------------------------------------------------------------------------- /app/Console/Commands/FlushFfmpegProcessCache.php: -------------------------------------------------------------------------------- 1 | info('🗑️ Flushing FFmpeg process cache.'); 31 | 32 | // Flush the Redis store (FFmpeg processes mgmt., cache, etc.) 33 | Redis::flushdb(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Console/Commands/GenerateAppKey.php: -------------------------------------------------------------------------------- 1 | info("🔑 App key check confirmed\n"); 31 | return; 32 | } else { 33 | $this->info("🔑 App key not found, generating one now...\n"); 34 | $this->call('key:generate', [ 35 | '--force' => true, 36 | ]); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Console/Commands/RestartQueue.php: -------------------------------------------------------------------------------- 1 | info("🔄 Restarting Horizon queue...\n"); 29 | $this->call('horizon:terminate'); 30 | $this->call('queue:flush'); 31 | $this->info("✅ Horizon queue restarted\n"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Console/Commands/ShowConfig.php: -------------------------------------------------------------------------------- 1 | argument('keys'); 16 | 17 | $output = []; 18 | 19 | if (empty($keys)) { 20 | $output = config()->all(); 21 | } else { 22 | foreach ($keys as $key) { 23 | $value = config($key); 24 | 25 | if (is_null($value)) { 26 | $this->warn("Key not found: $key"); 27 | continue; 28 | } 29 | 30 | Arr::set($output, $key, $value); 31 | } 32 | } 33 | 34 | $this->line(json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 35 | return Command::SUCCESS; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Enums/ChannelLogoType.php: -------------------------------------------------------------------------------- 1 | 'success', 14 | self::Epg => 'gray', 15 | }; 16 | } 17 | 18 | public function getLabel(): ?string 19 | { 20 | return match ($this) { 21 | self::Channel => 'Channel', 22 | self::Epg => 'EPG', 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Enums/PlaylistChannelId.php: -------------------------------------------------------------------------------- 1 | 'success', 16 | self::ChannelId => 'gray', 17 | self::Name => 'gray', 18 | self::Title => 'gray', 19 | }; 20 | } 21 | 22 | public function getLabel(): ?string 23 | { 24 | return match ($this) { 25 | self::TvgId => 'TVG ID/Stream ID (default)', 26 | self::ChannelId => 'Channel Number', 27 | self::Name => 'Channel Name', 28 | self::Title => 'Channel Title', 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Enums/Status.php: -------------------------------------------------------------------------------- 1 | 'gray', 16 | self::Processing => 'warning', 17 | self::Completed => 'success', 18 | self::Failed => 'danger', 19 | }; 20 | } 21 | } -------------------------------------------------------------------------------- /app/Events/CustomPlaylistCreated.php: -------------------------------------------------------------------------------- 1 | slideOver() 20 | ->successRedirectUrl(fn($record): string => EditCustomPlaylist::getUrl(['record' => $record])), 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Filament/Resources/EpgChannelResource/Pages/CreateEpgChannel.php: -------------------------------------------------------------------------------- 1 | where('user_id', auth()->id()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Filament/Resources/EpgMapResource/Pages/CreateEpgMap.php: -------------------------------------------------------------------------------- 1 | slideOver(), 19 | ]; 20 | } 21 | 22 | /** 23 | * @deprecated Override the `table()` method to configure the table. 24 | */ 25 | protected function getTableQuery(): ?Builder 26 | { 27 | return static::getResource()::getEloquentQuery() 28 | ->where('user_id', auth()->id()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Filament/Resources/GroupResource/Pages/CreateGroup.php: -------------------------------------------------------------------------------- 1 | slideOver() 18 | ->successRedirectUrl(fn($record): string => EditMergedPlaylist::getUrl(['record' => $record])), 19 | 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Filament/Resources/PlaylistAuthResource/Pages/CreatePlaylistAuth.php: -------------------------------------------------------------------------------- 1 | id(); 16 | return $data; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Filament/Resources/PlaylistAuthResource/Pages/EditPlaylistAuth.php: -------------------------------------------------------------------------------- 1 | dispatch('refreshRelation'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Filament/Resources/PlaylistResource/Pages/CreatePlaylist.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 18 | } 19 | 20 | protected function getSteps(): array 21 | { 22 | return PlaylistResource::getFormSteps(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Filament/Resources/PlaylistResource/Pages/ListPlaylists.php: -------------------------------------------------------------------------------- 1 | where('user_id', auth()->id()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Filament/Resources/PostProcessResource/Pages/CreatePostProcess.php: -------------------------------------------------------------------------------- 1 | id(); 16 | return $data; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Filament/Resources/PostProcessResource/Pages/EditPostProcess.php: -------------------------------------------------------------------------------- 1 | dispatch('refreshRelation'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Filament/Resources/SeriesResource/Pages/CreateSeries.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 18 | } 19 | 20 | protected function getSteps(): array 21 | { 22 | return SeriesResource::getFormSteps(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Filament/Widgets/DiscordWidget.php: -------------------------------------------------------------------------------- 1 | 1, 11 | 'lg' => 2, 12 | ]; 13 | 14 | protected static string $view = 'filament.widgets.ko-fi-widget'; 15 | } 16 | -------------------------------------------------------------------------------- /app/Filament/Widgets/PayPalDonateWidget.php: -------------------------------------------------------------------------------- 1 | versionData = [ 21 | 'version' => config('dev.version'), 22 | 'repo' => config('dev.repo'), 23 | 'latestVersion' => $latestVersion, 24 | 'updateAvailable' => $updateAvailable, 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Forms/Components/MediaFlowProxyUrl.php: -------------------------------------------------------------------------------- 1 | all(); 12 | return response()->json([ 13 | 'message' => 'Webhook received', 14 | 'method' => $request->method(), 15 | 'data' => $data 16 | ]); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Infolists/Components/SeriesPreview.php: -------------------------------------------------------------------------------- 1 | first(); 18 | if ($user) { 19 | $exception = $event->exception; 20 | $message = "Backup failed, error: \"{$exception->getMessage()}\""; 21 | Notification::make() 22 | ->danger() 23 | ->title("Backup failed") 24 | ->body($message) 25 | ->broadcast($user); 26 | Notification::make() 27 | ->danger() 28 | ->title("Backup failed") 29 | ->body($message) 30 | ->sendToDatabase($user); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Listeners/CustomPlaylistListener.php: -------------------------------------------------------------------------------- 1 | streamStats = $record->stream_stats; 15 | } 16 | 17 | public function placeholder() 18 | { 19 | return <<<'HTML' 20 |
21 | 22 |

Fetching stream stats, hold tight...

23 |
24 | HTML; 25 | } 26 | 27 | public function render() 28 | { 29 | return view('livewire.channel-stream-stats'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Livewire/ProfileComponent.php: -------------------------------------------------------------------------------- 1 | getNameComponent(), 17 | //$this->getEmailComponent(), 18 | ])->columnSpan(2); 19 | 20 | return ($this->hasAvatars) 21 | ? [filament('filament-breezy')->getAvatarUploadComponent(), $groupFields] 22 | : [$groupFields]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Models/Category.php: -------------------------------------------------------------------------------- 1 | 'integer', 21 | 'source_category_id' => 'integer', 22 | 'user_id' => 'integer', 23 | 'playlist_id' => 'integer', 24 | ]; 25 | 26 | public function user(): BelongsTo 27 | { 28 | return $this->belongsTo(User::class); 29 | } 30 | 31 | public function playlist(): BelongsTo 32 | { 33 | return $this->belongsTo(Playlist::class); 34 | } 35 | 36 | public function series(): HasMany 37 | { 38 | return $this->hasMany(Series::class); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Models/ChannelFailover.php: -------------------------------------------------------------------------------- 1 | 'integer', 20 | 'user_id' => 'integer', 21 | 'channel_id' => 'integer', 22 | 'channel_failover_id' => 'integer', 23 | 'metadata' => 'array', 24 | ]; 25 | 26 | public function user(): BelongsTo 27 | { 28 | return $this->belongsTo(User::class); 29 | } 30 | 31 | public function channel(): BelongsTo 32 | { 33 | return $this->belongsTo(Channel::class); 34 | } 35 | 36 | public function channelFailover(): BelongsTo 37 | { 38 | return $this->belongsTo(Channel::class, 'channel_failover_id'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Models/EpgChannel.php: -------------------------------------------------------------------------------- 1 | 'integer', 22 | 'epg_id' => 'integer', 23 | 'user_id' => 'integer', 24 | ]; 25 | 26 | protected $with = ['epg']; 27 | 28 | public function user(): BelongsTo 29 | { 30 | return $this->belongsTo(User::class); 31 | } 32 | 33 | public function epg(): BelongsTo 34 | { 35 | return $this->belongsTo(Epg::class) 36 | ->select(['id', 'name']); 37 | } 38 | 39 | public function channels(): HasMany 40 | { 41 | return $this->hasMany(Channel::class); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Models/EpgMap.php: -------------------------------------------------------------------------------- 1 | 'integer', 21 | 'processing' => 'boolean', 22 | // 'override' => 'boolean', 23 | 'progress' => 'float', 24 | 'user_id' => 'integer', 25 | 'epg_id' => 'integer', 26 | 'channel_count' => 'integer', 27 | 'mapped_count' => 'integer', 28 | 'status' => Status::class, 29 | ]; 30 | 31 | public function user(): BelongsTo 32 | { 33 | return $this->belongsTo(User::class); 34 | } 35 | 36 | public function epg(): BelongsTo 37 | { 38 | return $this->belongsTo(Epg::class); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Models/Job.php: -------------------------------------------------------------------------------- 1 | 'array', 20 | 'variables' => 'array', 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /app/Models/PlaylistSyncStatusLog.php: -------------------------------------------------------------------------------- 1 | 'integer', 20 | 'user_id' => 'integer', 21 | 'playlist_id' => 'integer', 22 | 'playlist_sync_status_id' => 'integer', 23 | 'meta' => 'array', 24 | ]; 25 | 26 | public function user(): BelongsTo 27 | { 28 | return $this->belongsTo(User::class); 29 | } 30 | 31 | public function playlist(): BelongsTo 32 | { 33 | return $this->belongsTo(Playlist::class); 34 | } 35 | 36 | public function playlistSyncStatus(): BelongsTo 37 | { 38 | return $this->belongsTo(PlaylistSyncStatus::class); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Models/PostProcessLog.php: -------------------------------------------------------------------------------- 1 | belongsTo(PostProcess::class); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Models/SourceGroup.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class); 14 | } 15 | 16 | public function playlist() 17 | { 18 | return $this->belongsTo(Playlist::class); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Pivots/CustomPlaylistPivot.php: -------------------------------------------------------------------------------- 1 | belongsTo(Channel::class); 19 | } 20 | 21 | public function enabledChannels(): HasManyThrough 22 | { 23 | return $this->channels()->where('enabled', true); 24 | } 25 | 26 | public function customPlaylist(): BelongsTo 27 | { 28 | return $this->belongsTo(CustomPlaylist::class); 29 | } 30 | 31 | public function playlists(): HasManyThrough 32 | { 33 | return $this->hasManyThrough( 34 | Playlist::class, 35 | Channel::class 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Pivots/MergedPlaylistPivot.php: -------------------------------------------------------------------------------- 1 | belongsTo(Playlist::class); 19 | } 20 | 21 | public function mergedPlaylist(): BelongsTo 22 | { 23 | return $this->belongsTo(MergedPlaylist::class); 24 | } 25 | 26 | public function channels(): HasManyThrough 27 | { 28 | return $this->hasManyThrough( 29 | Channel::class, 30 | Playlist::class 31 | ); 32 | } 33 | 34 | public function enabledChannels(): HasManyThrough 35 | { 36 | return $this->channels()->where('enabled', true); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Rules/Cron.php: -------------------------------------------------------------------------------- 1 | timezone) { 24 | $date->setTimezone($this->timezone); 25 | } 26 | 27 | return (new CronExpression($this->expression))->isDue($date->toDateTimeString()); 28 | } 29 | 30 | public function nextDue() 31 | { 32 | return Carbon::instance((new CronExpression($this->expression))->getNextRunDate()); 33 | } 34 | 35 | public function lastDue() 36 | { 37 | return Carbon::instance((new CronExpression($this->expression))->getPreviousRunDate()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withRouting( 9 | web: __DIR__ . '/../routes/web.php', 10 | api: __DIR__ . '/../routes/api.php', 11 | commands: __DIR__ . '/../routes/console.php', 12 | channels: __DIR__ . '/../routes/channels.php', 13 | health: '/up', 14 | ) 15 | ->withMiddleware(function (Middleware $middleware) { 16 | $middleware 17 | ->redirectGuestsTo('login') 18 | ->trustProxies(at: ['*']) 19 | ->validateCsrfTokens(except: [ 20 | 'webhook/test', 21 | ]); 22 | }) 23 | ->withExceptions(function (Exceptions $exceptions) { 24 | // 25 | })->create(); 26 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | [ 16 | \Wnx\LaravelBackupRestore\HealthChecks\Checks\DatabaseHasTables::class, 17 | ], 18 | ]; 19 | -------------------------------------------------------------------------------- /config/cors.php: -------------------------------------------------------------------------------- 1 | ['*'], 19 | 20 | 'allowed_methods' => ['*'], 21 | 22 | 'allowed_origins' => ['*'], 23 | 24 | 'allowed_origins_patterns' => [], 25 | 26 | 'allowed_headers' => ['*'], 27 | 28 | 'exposed_headers' => [], 29 | 30 | 'max_age' => 0, 31 | 32 | 'supports_credentials' => false, 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /config/proxy.php: -------------------------------------------------------------------------------- 1 | env('PROXY_URL_OVERRIDE', null), 5 | 'proxy_format' => env('PROXY_FORMAT', 'mpts'), // 'mpts' or 'hls' 6 | 'ffmpeg_path' => env('PROXY_FFMPEG_PATH', null), 7 | 'ffmpeg_additional_args' => env('PROXY_FFMPEG_ADDITIONAL_ARGS', ''), 8 | 'ffmpeg_codec_video' => env('PROXY_FFMPEG_CODEC_VIDEO', null), 9 | 'ffmpeg_codec_audio' => env('PROXY_FFMPEG_CODEC_AUDIO', null), 10 | 'ffmpeg_codec_subtitles' => env('PROXY_FFMPEG_CODEC_SUBTITLES', null), 11 | ]; -------------------------------------------------------------------------------- /config/xtream.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'series' => env('XTREAM_SERIES_FOLDER', 'Series'), 8 | 'movies' => env('XTREAM_MOVIE_FOLDER', 'Movies'), 9 | ], 10 | 11 | // Lang stripping 12 | 'lang_strip' => [ 13 | 'EN', 14 | 'FR', 15 | 'ES', 16 | 'DE', 17 | 'IT', 18 | 'PT', 19 | 'NL', 20 | 'RU', 21 | 'AR', 22 | 'TR', 23 | 'PL', 24 | 'JP', 25 | 'CN', 26 | ], 27 | 28 | // base strm output path (inside storage/app/private/[output_path]/[playlist_id]) 29 | 'output_path' => env('XTREAM_STRM_FOLDER', 'strm'), 30 | ]; 31 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /database/factories/CategoryFactory.php: -------------------------------------------------------------------------------- 1 | fake()->name(), 27 | 'name_internal' => fake()->word(), 28 | 'source_category_id' => fake()->randomNumber(), 29 | 'user_id' => User::factory(), 30 | 'playlist_id' => Playlist::factory(), 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/factories/ChannelFailoverFactory.php: -------------------------------------------------------------------------------- 1 | User::factory(), 27 | 'channel_id' => Channel::factory(), 28 | 'channel_failover_id' => Channel::factory()->create()->failover_id, 29 | 'sort' => fake()->randomNumber(), 30 | 'metadata' => '{}', 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/factories/CustomPlaylistFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name(), 26 | 'uuid' => $this->faker->uuid(), 27 | 'user_id' => User::factory(), 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/factories/EpgChannelFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name(), 26 | 'display_name' => $this->faker->word(), 27 | 'lang' => $this->faker->word(), 28 | 'channel_id' => $this->faker->word(), 29 | 'epg_id' => Epg::factory(), 30 | 'programmes' => $this->faker->text(), 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/factories/EpgFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name(), 26 | 'url' => $this->faker->url(), 27 | 'user_id' => User::factory(), 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/factories/EpgMapFactory.php: -------------------------------------------------------------------------------- 1 | fake()->name(), 27 | 'uuid' => fake()->uuid(), 28 | 'errors' => fake()->text(), 29 | 'status' => fake()->randomElement(["pending","processing","completed","failed"]), 30 | 'processing' => fake()->boolean(), 31 | 'progress' => fake()->randomFloat(0, 0, 9999999999.), 32 | 'sync_time' => fake()->dateTime(), 33 | 'user_id' => User::factory(), 34 | 'epg_id' => Epg::factory(), 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/factories/GroupFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name(), 27 | 'user_id' => User::factory(), 28 | 'playlist_id' => Playlist::factory(), 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/factories/MergedPlaylistFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name(), 26 | 'uuid' => $this->faker->uuid(), 27 | 'user_id' => User::factory(), 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/factories/PlaylistAuthFactory.php: -------------------------------------------------------------------------------- 1 | fake()->name(), 26 | 'enabled' => fake()->boolean(), 27 | 'user_id' => User::factory(), 28 | 'username' => fake()->userName(), 29 | 'password' => fake()->password(), 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/factories/PlaylistFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name(), 26 | 'uuid' => $this->faker->uuid(), 27 | 'url' => $this->faker->url(), 28 | 'status' => $this->faker->randomElement(["pending","processing","completed","failed"]), 29 | 'prefix' => $this->faker->word(), 30 | 'channels' => $this->faker->randomNumber(), 31 | 'synced' => $this->faker->dateTime(), 32 | 'errors' => $this->faker->text(), 33 | 'user_id' => User::factory(), 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/factories/PlaylistSyncStatusFactory.php: -------------------------------------------------------------------------------- 1 | fake()->name(), 27 | 'user_id' => User::factory(), 28 | 'playlist_id' => Playlist::factory(), 29 | 'deleted_groups' => fake()->text(), 30 | 'added_groups' => fake()->text(), 31 | 'deleted_channels' => fake()->text(), 32 | 'added_channels' => fake()->text(), 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/factories/PostProcessFactory.php: -------------------------------------------------------------------------------- 1 | fake()->name(), 26 | 'enabled' => fake()->boolean(), 27 | 'user_id' => User::factory(), 28 | 'event' => fake()->word(), 29 | 'metadata' => '{}', 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/0001_01_01_000001_create_cache_table.php: -------------------------------------------------------------------------------- 1 | string('key')->primary(); 16 | $table->mediumText('value'); 17 | $table->integer('expiration'); 18 | }); 19 | 20 | Schema::create('cache_locks', function (Blueprint $table) { 21 | $table->string('key')->primary(); 22 | $table->string('owner'); 23 | $table->integer('expiration'); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('cache'); 33 | Schema::dropIfExists('cache_locks'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /database/migrations/2020_02_12_008432_update_short_url_visits_table_for_version_two_zero_zero.php: -------------------------------------------------------------------------------- 1 | table('short_url_visits', function (Blueprint $table) { 17 | $table->string('referer_url')->after('browser_version')->nullable(); 18 | $table->string('device_type')->after('referer_url')->nullable(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::connection(config('short-url.connection'))->table('short_url_visits', function (Blueprint $table) { 30 | $table->dropColumn(['referer_url', 'device_type']); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2020_04_10_224546_update_short_url_table_for_version_three_zero_zero.php: -------------------------------------------------------------------------------- 1 | table('short_urls', function (Blueprint $table) { 17 | $table->timestamp('activated_at')->after('track_device_type')->nullable()->default(now()); 18 | $table->timestamp('deactivated_at')->after('activated_at')->nullable(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::connection(config('short-url.connection'))->table('short_urls', function (Blueprint $table) { 30 | $table->dropColumn(['activated_at', 'deactivated_at']); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2020_04_20_009283_update_short_url_table_add_option_to_forward_query_params.php: -------------------------------------------------------------------------------- 1 | table('short_urls', function (Blueprint $table) { 17 | $table->boolean('forward_query_params')->after('single_use')->default(false); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::connection(config('short-url.connection'))->table('short_urls', function (Blueprint $table) { 29 | $table->dropColumn(['forward_query_params']); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2022_12_14_083707_create_settings_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | 14 | $table->string('group'); 15 | $table->string('name'); 16 | $table->boolean('locked')->default(false); 17 | $table->json('payload'); 18 | 19 | $table->timestamps(); 20 | 21 | $table->unique(['group', 'name']); 22 | }); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /database/migrations/2024_12_17_203030_create_admin_user.php: -------------------------------------------------------------------------------- 1 | where('name', 'admin')->first(); 16 | if (!$user) { 17 | User::query()->create([ 18 | 'name' => 'admin', 19 | 'email' => 'admin@test.com', 20 | 'password' => bcrypt('admin') 21 | ]); 22 | } 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void {} 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2024_12_18_233316_create_notifications_table.php: -------------------------------------------------------------------------------- 1 | uuid('id')->primary(); 16 | $table->string('type'); 17 | $table->morphs('notifiable'); 18 | $table->text('data'); 19 | $table->timestamp('read_at')->nullable(); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('notifications'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /database/migrations/2024_12_19_034848_create_failed_import_rows_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->json('data'); 17 | $table->foreignId('import_id')->constrained()->cascadeOnDelete(); 18 | $table->text('validation_error')->nullable(); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | */ 26 | public function down(): void 27 | { 28 | Schema::dropIfExists('failed_import_rows'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2024_12_19_154248_create_groups_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 20 | $table->foreignId('playlist_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 21 | $table->timestamps(); 22 | }); 23 | 24 | Schema::enableForeignKeyConstraints(); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('groups'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/2025_01_06_161153_add_import_batch_no_to_channels.php: -------------------------------------------------------------------------------- 1 | string('import_batch_no')->nullable()->after('country'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('channels', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_01_19_220431_add_import_batch_no_to_groups.php: -------------------------------------------------------------------------------- 1 | string('import_batch_no')->nullable()->after('playlist_id'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('groups', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_01_21_164226_create_epgs_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->string('url')->nullable(); 20 | $table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 21 | $table->timestamps(); 22 | }); 23 | 24 | Schema::enableForeignKeyConstraints(); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('epgs'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/2025_01_21_170519_add_synced_column_to_epgs.php: -------------------------------------------------------------------------------- 1 | string('import_batch_no')->nullable()->after('url'); 16 | $table->dateTime('synced')->nullable()->after('user_id'); 17 | 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::table('epgs', function (Blueprint $table) { 27 | // 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2025_01_21_170900_add_import_batch_to_epg_channels.php: -------------------------------------------------------------------------------- 1 | string('import_batch_no')->nullable()->after('programmes'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epg_channels', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_01_21_171508_add_status_to_epg.php: -------------------------------------------------------------------------------- 1 | enum('status', ['pending', 'processing', 'completed', 'failed']) 18 | ->default('pending') 19 | ->after('url'); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | */ 26 | public function down(): void 27 | { 28 | Schema::table('epgs', function (Blueprint $table) { 29 | // 30 | }); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2025_01_21_171725_add_errors_columnd_to_epg.php: -------------------------------------------------------------------------------- 1 | longText('errors')->nullable()->after('synced'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epgs', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_01_21_173519_add_uploads_column_to_epg.php: -------------------------------------------------------------------------------- 1 | mediumText('uploads')->nullable()->after('url'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epgs', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_01_21_174319_add_user_id_to_epg_channels.php: -------------------------------------------------------------------------------- 1 | foreignId('user_id')->after('epg_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epg_channels', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_01_21_224113_add_icon_to_epg_channels.php: -------------------------------------------------------------------------------- 1 | string('icon')->nullable()->after('lang'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epg_channels', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_01_22_200738_remove_programme_column_from_epg_channels.php: -------------------------------------------------------------------------------- 1 | dropColumn('programmes'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epg_channels', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_01_23_204711_add_sync_time_to_playlists.php: -------------------------------------------------------------------------------- 1 | float('sync_time')->after('synced')->nullable(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_01_28_152132_create_merged_playlists_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->uuid('uuid'); 20 | $table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 21 | $table->timestamps(); 22 | }); 23 | 24 | Schema::enableForeignKeyConstraints(); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('merged_playlists'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/2025_01_28_152133_create_merged_playlist_playlist_table.php: -------------------------------------------------------------------------------- 1 | foreignId('merged_playlist_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 18 | $table->foreignId('playlist_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 19 | }); 20 | 21 | Schema::enableForeignKeyConstraints(); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('merged_playlist_playlist'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /database/migrations/2025_01_28_152137_create_custom_playlists_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->uuid('uuid'); 20 | $table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 21 | $table->timestamps(); 22 | }); 23 | 24 | Schema::enableForeignKeyConstraints(); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('custom_playlists'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/2025_01_28_152138_create_channel_custom_playlist_table.php: -------------------------------------------------------------------------------- 1 | foreignId('channel_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 18 | $table->foreignId('custom_playlist_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 19 | }); 20 | 21 | Schema::enableForeignKeyConstraints(); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('channel_custom_playlist'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /database/migrations/2025_01_28_225053_add_sync_time_to_epgs.php: -------------------------------------------------------------------------------- 1 | float('sync_time')->after('synced')->nullable(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epgs', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_01_28_225829_add_index_to_epg_channels.php: -------------------------------------------------------------------------------- 1 | unique($this->uniqueColumns); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::table('epg_channels', function (Blueprint $table) { 27 | $table->dropUnique($this->uniqueColumns); 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2025_01_29_190403_add_epg_channel_id_column_to_channels.php: -------------------------------------------------------------------------------- 1 | foreignId('epg_channel_id')->nullable()->after('stream_id')->constrained()->nullOnDelete()->nullOnUpdate(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('channels', function (Blueprint $table) { 25 | $table->dropForeign(['epg_channel_id']); 26 | $table->dropColumn('epg_channel_id'); 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2025_02_01_151321_drop_epg_programmes_table.php: -------------------------------------------------------------------------------- 1 | uuid('uuid')->after('name')->unique()->nullable(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epgs', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_06_161829_add_auto_sync_toggle_to_playlists.php: -------------------------------------------------------------------------------- 1 | boolean('auto_sync')->default(true)->after('channels'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_06_161839_add_auto_sync_toggle_to_epgs.php: -------------------------------------------------------------------------------- 1 | boolean('auto_sync')->default(true)->after('user_id'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epgs', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_09_165446_add_avatar_url_to_users.php: -------------------------------------------------------------------------------- 1 | string('avatar_url')->nullable()->after('email'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('users', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_09_215228_add_progress_column_to_playlists.php: -------------------------------------------------------------------------------- 1 | float('progress')->default(100)->after('status'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_09_215233_add_progress_column_to_playlists.php: -------------------------------------------------------------------------------- 1 | float('progress')->default(100)->after('status'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epgs', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_13_171533_add_processing_flag_to_playlists.php: -------------------------------------------------------------------------------- 1 | boolean('processing')->after('status')->default(false); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_13_171537_add_processing_flag_to_epgs.php: -------------------------------------------------------------------------------- 1 | boolean('processing')->after('status')->default(false); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epgs', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_13_215803_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | dropIfExists('jobs'); 15 | Schema::connection('jobs')->create('jobs', function (Blueprint $table) { 16 | $table->id(); 17 | $table->string('title'); 18 | $table->string('batch_no'); 19 | $table->longText('payload'); 20 | $table->json('variables')->nullable(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::connection('jobs')->dropIfExists('jobs'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2025_02_13_224233_add_sync_interval_to_playlists.php: -------------------------------------------------------------------------------- 1 | string('sync_interval')->default('24hr')->after('synced'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_13_224236_add_sync_interval_to_epgs.php: -------------------------------------------------------------------------------- 1 | string('sync_interval')->default('24hr')->after('synced'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epgs', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_14_214426_add_playlist_import_prefs_column_to_playlists.php: -------------------------------------------------------------------------------- 1 | json('import_prefs')->after('sync_interval')->nullable(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_14_222330_add_groups_column_to_playlists.php: -------------------------------------------------------------------------------- 1 | longText('groups')->after('import_prefs')->nullable(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_15_150422_reset_playlist_interval_on_playlists.php: -------------------------------------------------------------------------------- 1 | update(['sync_interval' => '24 hours']); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | // 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /database/migrations/2025_02_15_150833_reset_playlist_interval_on_epgs.php: -------------------------------------------------------------------------------- 1 | update(['sync_interval' => '24 hours']); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | // 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /database/migrations/2025_02_16_184838_add_counts_to_epg_maps.php: -------------------------------------------------------------------------------- 1 | integer('channel_count')->after('sync_time')->default(0); 16 | $table->integer('mapped_count')->after('channel_count')->default(0); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('epg_maps', function (Blueprint $table) { 26 | // 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2025_02_16_193535_add_override_column_to_epg_maps.php: -------------------------------------------------------------------------------- 1 | boolean('override')->after('mapped_count')->default(false); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epg_maps', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_18_173424_add_enable_toggle_to_playlists.php: -------------------------------------------------------------------------------- 1 | boolean('enable_channels')->after('auto_sync') 16 | ->default(false); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('playlists', function (Blueprint $table) { 26 | // 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2025_02_18_185618_add_internal_group_name_to_groups.php: -------------------------------------------------------------------------------- 1 | string('name_internal')->after('name')->nullable(); 18 | }); 19 | 20 | // 2. copying the existing column values into new one using Laravel's query builder 21 | DB::table('groups')->update(['name_internal' => DB::raw('name')]); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::table('groups', function (Blueprint $table) { 30 | // 31 | }); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2025_02_19_152211_add_is_custom_column_to_groups.php: -------------------------------------------------------------------------------- 1 | boolean('custom')->after('name_internal')->default(false); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('groups', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_21_155914_drop_queue_monitors_table.php: -------------------------------------------------------------------------------- 1 | mediumText('uploads')->nullable()->after('url'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_02_26_200928_add_xtream_columns_to_playlists.php: -------------------------------------------------------------------------------- 1 | boolean('xtream')->after('uploads')->default(false); 16 | $table->json('xtream_config')->after('xtream')->nullable(); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('playlists', function (Blueprint $table) { 26 | // 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2025_02_27_221823_add_user_agent_ssl_prefs_to_playlists.php: -------------------------------------------------------------------------------- 1 | text('user_agent') 16 | ->after('import_prefs') 17 | ->nullable(); 18 | 19 | $table->boolean('disable_ssl_verification') 20 | ->default(false) 21 | ->after('user_agent'); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::table('playlists', function (Blueprint $table) { 31 | // 32 | }); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/2025_02_27_221833_add_user_agent_ssl_prefs_to_epgs.php: -------------------------------------------------------------------------------- 1 | text('user_agent') 16 | ->after('sync_interval') 17 | ->nullable(); 18 | 19 | $table->boolean('disable_ssl_verification') 20 | ->default(false) 21 | ->after('user_agent'); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::table('epgs', function (Blueprint $table) { 31 | // 32 | }); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/2025_02_27_224518_add_default_user_agent_to_playlists_and_epgs.php: -------------------------------------------------------------------------------- 1 | update([ 14 | 'user_agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 15 | ]); 16 | DB::table('epgs')->update([ 17 | 'user_agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13', 18 | ]); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | // 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_04_140444_add_editable_attributes_to_channels.php: -------------------------------------------------------------------------------- 1 | string('title_custom')->nullable()->after('title'); 16 | $table->string('name_custom')->nullable()->after('name'); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('channels', function (Blueprint $table) { 26 | // 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2025_03_04_145644_add_icon_preference_toggle_to_channels.php: -------------------------------------------------------------------------------- 1 | string('logo_type')->default('channel')->after('logo'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('channels', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_05_142409_add_recurring_toggle_to_epg_maps.php: -------------------------------------------------------------------------------- 1 | boolean('recurring')->after('override')->default(false); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epg_maps', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_05_145318_add_playlist_id_column_to_epg_maps.php: -------------------------------------------------------------------------------- 1 | foreignId('playlist_id')->nullable()->after('epg_id')->constrained()->nullOnDelete(); 15 | }); 16 | } 17 | 18 | /** 19 | * Reverse the migrations. 20 | */ 21 | public function down(): void 22 | { 23 | Schema::table('epg_maps', function (Blueprint $table) { 24 | // 25 | }); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /database/migrations/2025_03_05_165633_add_mapped_at_column_to_epg_maps.php: -------------------------------------------------------------------------------- 1 | dateTime('mapped_at')->nullable()->after('progress'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epg_maps', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_06_155450_add_custom_id_and_url_to_channels.php: -------------------------------------------------------------------------------- 1 | mediumText('url_custom')->nullable()->after('url'); 16 | $table->string('stream_id_custom')->nullable()->after('stream_id'); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('channels', function (Blueprint $table) { 26 | // 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2025_03_07_153936_add_sort_column_to_channels.php: -------------------------------------------------------------------------------- 1 | decimal('sort', 12, 4) 16 | ->after('group_id') 17 | ->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::table('channels', function (Blueprint $table) { 27 | // 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2025_03_09_161832_add_xtream_status_to_playlists.php: -------------------------------------------------------------------------------- 1 | json('xtream_status')->after('xtream_config')->nullable(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_10_205435_add_extvlcopt_column_to_channels.php: -------------------------------------------------------------------------------- 1 | json('extvlcopt')->nullable()->after('stream_id'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('channels', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_12_215809_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->morphs('tokenable'); 17 | $table->string('name'); 18 | $table->string('token', 64)->unique(); 19 | $table->text('abilities')->nullable(); 20 | $table->timestamp('last_used_at')->nullable(); 21 | $table->timestamp('expires_at')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('personal_access_tokens'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2025_03_12_220659_create_breezy_sessions_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->morphs('authenticatable'); 14 | $table->string('panel_id')->nullable(); 15 | $table->string('guard')->nullable(); 16 | $table->string('ip_address', 45)->nullable(); 17 | $table->text('user_agent')->nullable(); 18 | $table->timestamp('expires_at')->nullable(); 19 | $table->text('two_factor_secret')->nullable(); 20 | $table->text('two_factor_recovery_codes')->nullable(); 21 | $table->timestamp('two_factor_confirmed_at')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | 25 | } 26 | 27 | public function down() 28 | { 29 | Schema::dropIfExists('breezy_sessions'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /database/migrations/2025_03_18_132548_add_auto_sort_order_to_playlists.php: -------------------------------------------------------------------------------- 1 | boolean('auto_sort')->after('auto_sync')->default(false); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_19_161419_add_enable_proxy_to_playlists.php: -------------------------------------------------------------------------------- 1 | boolean('enable_proxy')->default(false)->after('enable_channels'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_19_161433_add_enable_proxy_to_custom_playlists.php: -------------------------------------------------------------------------------- 1 | boolean('enable_proxy')->default(false)->after('channel_start'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('custom_playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_19_161447_add_enable_proxy_to_merged_playlists.php: -------------------------------------------------------------------------------- 1 | boolean('enable_proxy')->default(false)->after('channel_start'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('merged_playlists', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_20_174644_add_kodi_drop_column_to_channels.php: -------------------------------------------------------------------------------- 1 | json('kodidrop')->nullable()->after('extvlcopt'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('channels', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_25_204141_add_preferred_local_to_epg.php: -------------------------------------------------------------------------------- 1 | string('preferred_local')->nullable()->after('name'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('epgs', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_03_31_212827_create_playlist_auths_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->boolean('enabled')->default(true); 20 | $table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 21 | $table->string('username'); 22 | $table->string('password'); 23 | $table->timestamps(); 24 | }); 25 | 26 | Schema::enableForeignKeyConstraints(); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | */ 32 | public function down(): void 33 | { 34 | Schema::dropIfExists('playlist_auths'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2025_03_31_221549_add_authable_pivot_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('playlist_auth_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 19 | $table->morphs('authenticatable'); 20 | $table->timestamps(); 21 | }); 22 | 23 | Schema::enableForeignKeyConstraints(); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('authenticatables'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2025_04_19_175837_create_tag_tables.php: -------------------------------------------------------------------------------- 1 | id(); 13 | 14 | $table->json('name'); 15 | $table->json('slug'); 16 | $table->string('type')->nullable(); 17 | $table->integer('order_column')->nullable(); 18 | 19 | $table->timestamps(); 20 | }); 21 | 22 | Schema::create('taggables', function (Blueprint $table) { 23 | $table->foreignId('tag_id')->constrained()->cascadeOnDelete(); 24 | 25 | $table->morphs('taggable'); 26 | 27 | $table->unique(['tag_id', 'taggable_id', 'taggable_type']); 28 | }); 29 | } 30 | 31 | public function down(): void 32 | { 33 | Schema::dropIfExists('taggables'); 34 | Schema::dropIfExists('tags'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2025_04_21_203422_create_post_processes_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->boolean('enabled')->default(true); 20 | $table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 21 | $table->string('event'); 22 | $table->json('metadata'); 23 | $table->timestamps(); 24 | }); 25 | 26 | Schema::enableForeignKeyConstraints(); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | */ 32 | public function down(): void 33 | { 34 | Schema::dropIfExists('post_processes'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2025_04_21_203508_create_processables_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('post_process_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 19 | $table->morphs('processable'); 20 | $table->timestamps(); 21 | }); 22 | 23 | Schema::enableForeignKeyConstraints(); 24 | 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('processables'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/2025_04_21_213721_crate_post_processing_logs_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('post_process_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 19 | $table->string('name'); 20 | $table->string('type'); 21 | $table->string('status'); 22 | $table->string('message')->nullable(); 23 | $table->timestamps(); 24 | }); 25 | 26 | Schema::enableForeignKeyConstraints(); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | */ 32 | public function down(): void 33 | { 34 | Schema::dropIfExists('post_process_logs'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2025_04_24_205934_add_new_column_to_channels.php: -------------------------------------------------------------------------------- 1 | boolean('new')->default(false)->after('enabled'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('channels', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_04_24_205937_add_new_column_to_groups.php: -------------------------------------------------------------------------------- 1 | boolean('new')->default(false)->after('custom'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('groups', function (Blueprint $table) { 25 | // 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_04_24_214454_add_stats_meta_to_playlist_sync_status.php: -------------------------------------------------------------------------------- 1 | json('sync_stats')->nullable()->after('playlist_id'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('playlist_sync_statuses', function (Blueprint $table) { 25 | $table->dropColumn('sync_stats'); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_04_26_124521_add_dummy_epg_category_to_playlists.php: -------------------------------------------------------------------------------- 1 | boolean('dummy_epg_category') 16 | ->default(false) 17 | ->after('dummy_epg_length'); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::table('playlists', function (Blueprint $table) { 27 | $table->dropColumn('dummy_epg_category'); 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2025_05_05_180136_add_catchup_to_channels.php: -------------------------------------------------------------------------------- 1 | string('catchup')->nullable()->after('shift'); 16 | $table->string('catchup_source')->nullable()->after('catchup'); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('channels', function (Blueprint $table) { 26 | $table->dropColumn('catchup'); 27 | $table->dropColumn('catchup_source'); 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2025_05_07_202251_remove_batch_job_tables.php: -------------------------------------------------------------------------------- 1 | truncate(); 17 | DB::table('failed_jobs')->truncate(); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | // 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /database/migrations/2025_05_09_220958_update_epg_channel_title_column.php: -------------------------------------------------------------------------------- 1 | text('display_name')->nullable()->change(); 16 | $table->text('icon')->nullable()->change(); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | // 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /database/migrations/2025_05_21_185917_add_series_progress_to_playlists.php: -------------------------------------------------------------------------------- 1 | float('series_progress') 16 | ->default(100) 17 | ->after('progress'); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::table('playlists', function (Blueprint $table) { 27 | // 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2025_05_21_192755_add_source_series_id_to_series.php: -------------------------------------------------------------------------------- 1 | unsignedInteger('source_series_id') 16 | ->nullable() 17 | ->after('source_category_id'); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::table('series', function (Blueprint $table) { 27 | // 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2025_05_21_213847_update_date_time_column.php: -------------------------------------------------------------------------------- 1 | string('added')->nullable()->change(); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | // 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /database/migrations/2025_05_22_114818_add_sort_order_to_series.php: -------------------------------------------------------------------------------- 1 | decimal('sort', 12, 4) 16 | ->after('category_id') 17 | ->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::table('series', function (Blueprint $table) { 27 | // 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2025_05_22_201214_add_sync_location_to_series.php: -------------------------------------------------------------------------------- 1 | jsonb('sync_settings')->nullable()->after('enabled'); 16 | $table->string('sync_location')->nullable()->after('sync_settings'); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('series', function (Blueprint $table) { 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2025_05_30_152649_add_available_streams_to_playlists.php: -------------------------------------------------------------------------------- 1 | unsignedInteger('available_streams') 16 | ->default(0) 17 | ->after('streams'); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::table('playlists', function (Blueprint $table) { 27 | $table->dropColumn('available_streams'); 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2025_06_01_020531_add_sort_order_to_groups_table.php: -------------------------------------------------------------------------------- 1 | decimal('sort_order', 12, 4) 16 | ->after('playlist_id') 17 | ->default(9999); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::table('groups', function (Blueprint $table) { 27 | $table->dropColumn('sort_order'); 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 17 | 18 | User::factory()->create([ 19 | 'name' => 'Test User', 20 | 'email' => 'test@example.com', 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /database/settings/2025_05_01_160604_add_mfp_user_agent_fields.php: -------------------------------------------------------------------------------- 1 | migrator->exists('general.mediaflow_proxy_playlist_user_agent')) { 10 | $this->migrator->add('general.mediaflow_proxy_playlist_user_agent', true); 11 | } 12 | if (!$this->migrator->exists('general.mediaflow_proxy_user_agent')) { 13 | $this->migrator->add('general.mediaflow_proxy_user_agent', null); 14 | } 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /database/settings/2025_05_16_070340_add_ffmpeg_codec_fields.php: -------------------------------------------------------------------------------- 1 | migrator->exists('general.ffmpeg_codec_video')) { 10 | $this->migrator->add('general.ffmpeg_codec_video', config('proxy.ffmpeg_codec_video') ?? null); 11 | } 12 | if (!$this->migrator->exists('general.ffmpeg_codec_audio')) { 13 | $this->migrator->add('general.ffmpeg_codec_audio', config('proxy.ffmpeg_codec_audio') ?? null); 14 | } 15 | if (!$this->migrator->exists('general.ffmpeg_codec_subtitles')) { 16 | $this->migrator->add('general.ffmpeg_codec_subtitles', config('proxy.ffmpeg_codec_subtitles') ?? null); 17 | } 18 | } 19 | }; -------------------------------------------------------------------------------- /database/settings/2025_05_19_130544_add_ffmpeg_path_select_option.php: -------------------------------------------------------------------------------- 1 | migrator->exists('general.ffmpeg_path')) { 10 | $this->migrator->add('general.ffmpeg_path', config('proxy.ffmpeg_path') ?? null); 11 | } 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /database/settings/2025_05_29_180906_add_ffmpeg_custom_command_template_settings.php: -------------------------------------------------------------------------------- 1 | migrator->inGroup('general', function (SettingsBlueprint $blueprint): void { 11 | $blueprint->add('ffmpeg_custom_command_template', null); 12 | }); 13 | } 14 | 15 | public function down(): void 16 | { 17 | $this->migrator->inGroup('general', function (SettingsBlueprint $blueprint): void { 18 | $blueprint->delete('ffmpeg_custom_command_template'); 19 | }); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /database/settings/2025_06_03_190045_add_ffmpeg_hls_settings.php: -------------------------------------------------------------------------------- 1 | migrator->inGroup('general', function (SettingsBlueprint $blueprint): void { 11 | $blueprint->add('ffmpeg_hls_time', 4); 12 | $blueprint->add('ffmpeg_ffprobe_timeout', 5); 13 | $blueprint->add('hls_playlist_max_attempts', 10); 14 | $blueprint->add('hls_playlist_sleep_seconds', 1.0); 15 | }); 16 | } 17 | 18 | public function down(): void 19 | { 20 | $this->migrator->inGroup('general', function (SettingsBlueprint $blueprint): void { 21 | $blueprint->delete('ffmpeg_hls_time'); 22 | $blueprint->delete('ffmpeg_ffprobe_timeout'); 23 | $blueprint->delete('hls_playlist_max_attempts'); 24 | $blueprint->delete('hls_playlist_sleep_seconds'); 25 | }); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /docker/8.4/nginx/laravel.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen ${APP_PORT}; 3 | server_name _; 4 | 5 | root /var/www/html/public; 6 | index index.php index.html; 7 | 8 | charset utf-8; 9 | 10 | location / { 11 | try_files $uri $uri/ /index.php?$query_string; 12 | } 13 | 14 | location /favicon.ico { 15 | access_log off; 16 | log_not_found off; 17 | } 18 | location /robots.txt { 19 | access_log off; 20 | log_not_found off; 21 | } 22 | 23 | # HLS Streaming 24 | location /internal/hls/ { 25 | internal; 26 | alias /var/www/html/storage/app/hls/; 27 | access_log off; 28 | add_header Cache-Control no-cache; 29 | } 30 | 31 | error_page 404 /index.php; 32 | 33 | location ~ \.php$ { 34 | fastcgi_pass 127.0.0.1:${FPMPORT}; 35 | fastcgi_index index.php; 36 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 37 | include fastcgi_params; 38 | } 39 | 40 | location ~ /\.(?!well-known).* { 41 | deny all; 42 | } 43 | } -------------------------------------------------------------------------------- /docker/8.4/php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | upload_max_filesize = 1024M 3 | post_max_size = 1024M 4 | max_execution_time = 600 5 | max_input_time = 900 6 | memory_limit = 2048M 7 | variables_order = EGPCS 8 | pcov.directory = . 9 | # output_buffering = Off 10 | # implicit_flush = On -------------------------------------------------------------------------------- /docker/8.4/redis.conf: -------------------------------------------------------------------------------- 1 | bind 0.0.0.0 2 | port ${REDIS_SERVER_PORT} 3 | timeout 0 -------------------------------------------------------------------------------- /docker/mariadb/create-testing-database.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | /usr/bin/mariadb --user=root --password="$MYSQL_ROOT_PASSWORD" <<-EOSQL 4 | CREATE DATABASE IF NOT EXISTS testing; 5 | GRANT ALL PRIVILEGES ON \`testing%\`.* TO '$MYSQL_USER'@'%'; 6 | EOSQL 7 | -------------------------------------------------------------------------------- /docker/mysql/create-testing-database.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" <<-EOSQL 4 | CREATE DATABASE IF NOT EXISTS testing; 5 | GRANT ALL PRIVILEGES ON \`testing%\`.* TO '$MYSQL_USER'@'%'; 6 | EOSQL 7 | -------------------------------------------------------------------------------- /docker/pgsql/create-testing-database.sql: -------------------------------------------------------------------------------- 1 | SELECT 'CREATE DATABASE testing' 2 | WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'testing')\gexec 3 | -------------------------------------------------------------------------------- /draft.yml: -------------------------------------------------------------------------------- 1 | models: 2 | ChannelFailover: 3 | user_id: id foreign 4 | channel_id: id foreign 5 | channel_failover_id: id foreign:channels 6 | sort: unsigned integer 7 | metadata: jsonb 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "build": "vite build", 6 | "dev": "vite" 7 | }, 8 | "devDependencies": { 9 | "@tailwindcss/forms": "^0.5.10", 10 | "@tailwindcss/typography": "^0.5.16", 11 | "autoprefixer": "^10.4.20", 12 | "axios": "^1.7.4", 13 | "chokidar": "^4.0.3", 14 | "concurrently": "^9.0.1", 15 | "laravel-echo": "^1.17.1", 16 | "laravel-vite-plugin": "^1.0", 17 | "postcss": "^8.4.47", 18 | "pusher-js": "^8.4.0-rc2", 19 | "sass-embedded": "^1.85.0", 20 | "tailwindcss": "^3.4.13", 21 | "video.js": "^8.22.0", 22 | "vite": "^6.0" 23 | }, 24 | "dependencies": { 25 | "easyqrcodejs": "^4.6.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/css/aymanalhattami/filament-slim-scrollbar/filament-slim-scrollbar.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 768px) { 2 | ::-webkit-scrollbar { 3 | height: 5px; 4 | width: 5px 5 | } 6 | 7 | ::-webkit-scrollbar-track { 8 | background: #e5e7eb 9 | } 10 | 11 | ::-webkit-scrollbar-thumb { 12 | background: #d1d5db 13 | } 14 | 15 | ::-webkit-scrollbar-thumb:hover { 16 | background: #c9cbcd 17 | } 18 | 19 | .dark ::-webkit-scrollbar-track { 20 | background: #101215 21 | } 22 | 23 | .dark ::-webkit-scrollbar-thumb { 24 | background: #1f2126 25 | } 26 | 27 | .dark ::-webkit-scrollbar-thumb:hover { 28 | background: #1a1d21 29 | } 30 | 31 | @supports (-moz-appearance: none) { 32 | * { 33 | scrollbar-width: thin; 34 | scrollbar-color: #d1d5db #e5e7eb; 35 | } 36 | 37 | .dark, .dark * { 38 | scrollbar-color: #1f2126 #101215; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/public/favicon.png -------------------------------------------------------------------------------- /public/images/crypto-icons/litecoin.svg: -------------------------------------------------------------------------------- 1 | 2 | litecoin-ltc-logo -------------------------------------------------------------------------------- /public/images/crypto-icons/ripple.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/crypto-icons/tether.svg: -------------------------------------------------------------------------------- 1 | 2 | tether-usdt-logo -------------------------------------------------------------------------------- /public/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/images/xtream-codes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/public/images/xtream-codes.png -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 18 | -------------------------------------------------------------------------------- /public/js/devonab/filament-easy-footer/filament-easy-footer-scripts.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/public/js/devonab/filament-easy-footer/filament-easy-footer-scripts.js -------------------------------------------------------------------------------- /public/js/filament/forms/components/key-value.js: -------------------------------------------------------------------------------- 1 | function r({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/tags-input.js: -------------------------------------------------------------------------------- 1 | function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/textarea.js: -------------------------------------------------------------------------------- 1 | function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init:function(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight:function(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize:function(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver:function(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/public/logo.png -------------------------------------------------------------------------------- /public/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/public/placeholder.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/vendor/log-viewer/app.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * The buffer module from node.js, for the browser. 3 | * 4 | * @author Feross Aboukhadijeh 5 | * @license MIT 6 | */ 7 | 8 | /*! 9 | * pinia v2.2.2 10 | * (c) 2024 Eduardo San Martin Morote 11 | * @license MIT 12 | */ 13 | 14 | /*! #__NO_SIDE_EFFECTS__ */ 15 | 16 | /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ 17 | 18 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 19 | 20 | /** 21 | * @license 22 | * Lodash 23 | * Copyright OpenJS Foundation and other contributors 24 | * Released under MIT license 25 | * Based on Underscore.js 1.8.3 26 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 27 | */ 28 | 29 | /** 30 | * @vue/shared v3.4.38 31 | * (c) 2018-present Yuxi (Evan) You and Vue contributors 32 | * @license MIT 33 | **/ 34 | -------------------------------------------------------------------------------- /public/vendor/log-viewer/img/log-viewer-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/public/vendor/log-viewer/img/log-viewer-128.png -------------------------------------------------------------------------------- /public/vendor/log-viewer/img/log-viewer-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/public/vendor/log-viewer/img/log-viewer-32.png -------------------------------------------------------------------------------- /public/vendor/log-viewer/img/log-viewer-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/public/vendor/log-viewer/img/log-viewer-64.png -------------------------------------------------------------------------------- /public/vendor/log-viewer/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/app.js": "/app.js?id=6c51578cafcbf2f5e90fbf568a61ab8f", 3 | "/app.css": "/app.css?id=5593a0331dd40729ff41e32a6035d872", 4 | "/img/log-viewer-128.png": "/img/log-viewer-128.png?id=d576c6d2e16074d3f064e60fe4f35166", 5 | "/img/log-viewer-32.png": "/img/log-viewer-32.png?id=f8ec67d10f996aa8baf00df3b61eea6d", 6 | "/img/log-viewer-64.png": "/img/log-viewer-64.png?id=8902d596fc883ca9eb8105bb683568c6" 7 | } 8 | -------------------------------------------------------------------------------- /rebuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./vendor/bin/sail build --no-cache && ./vendor/bin/sail up --remove-orphans 4 | -------------------------------------------------------------------------------- /refresh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Refresh the data 4 | php artisan blueprint:erase && php artisan blueprint:build && php artisan migrate:fresh 5 | 6 | # Notify the user 7 | echo "=====================" 8 | echo " Data refreshed 🎉 " 9 | echo "=====================" -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | @import '../../vendor/filament/filament/resources/css/theme.css'; 2 | 3 | @import "./components/tables.scss"; 4 | @import "./components/widgets.css"; 5 | @import "./components/footer.css"; 6 | -------------------------------------------------------------------------------- /resources/css/components/footer.css: -------------------------------------------------------------------------------- 1 | 2 | .fi-footer > span { 3 | @apply text-center; 4 | } 5 | 6 | .fi-footer.fi-sidebar > span:not(:first-child) { 7 | @apply pl-0 pr-0 border-0; 8 | } 9 | 10 | .fi-footer > span:first-child { 11 | @apply pr-4 border-r border-gray-200 dark:border-gray-700; 12 | } 13 | 14 | .fi-footer:not(.fi-sidebar) > span:not(:first-child):not(:last-child) { 15 | @apply border-r border-gray-200 dark:border-gray-700; 16 | } 17 | 18 | .fi-footer > span:not(:first-child):not(:last-child) { 19 | @apply pl-0 pr-4; 20 | } 21 | 22 | .fi-footer > span:last-child { 23 | @apply border-0 pl-0 !important; 24 | } 25 | 26 | .fi-footer.fi-sidebar > span { 27 | @apply px-2; 28 | } 29 | 30 | .fi-footer.gap-4 > span { 31 | @apply px-4; 32 | } 33 | 34 | 35 | .fi-footer.fi-sidebar.gap-2 > span { 36 | @apply px-2; 37 | } 38 | 39 | .fi-footer.fi-sidebar.gap-2 > span:nth-child(2) 40 | { 41 | @apply pl-0; 42 | } -------------------------------------------------------------------------------- /resources/css/components/tables.scss: -------------------------------------------------------------------------------- 1 | table { 2 | 3 | .fi-input, 4 | .fi-select-input, 5 | .fi-ta-cell div { 6 | padding-top: 0px !important; 7 | padding-bottom: 0px !important; 8 | margin-top: 0px !important; 9 | margin-bottom: 0px !important; 10 | } 11 | 12 | .fi-ta-cell { 13 | padding-top: 4px !important; 14 | padding-bottom: 4px !important; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /resources/css/components/widgets.css: -------------------------------------------------------------------------------- 1 | .fi-wi-widget dl, 2 | .fi-wi-widget section, 3 | .fi-wi-widget .fi-fo-component-ctn { 4 | @apply h-full; 5 | } 6 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | // Import styles (Support HMR) 2 | import '../css/app.css' 3 | 4 | // Enable websockets 5 | import './echo' 6 | 7 | // Vendor 8 | import './vendor/qrcode' 9 | import './vendor/video' 10 | 11 | // Fix broken images 12 | document.addEventListener('error', event => { 13 | const el = event.target; 14 | if (el.tagName.toLowerCase() === 'img') { 15 | el.onerror = null; 16 | el.src = '/placeholder.png'; 17 | } 18 | }, true); -------------------------------------------------------------------------------- /resources/js/echo.js: -------------------------------------------------------------------------------- 1 | import Echo from 'laravel-echo'; 2 | import Pusher from 'pusher-js'; 3 | const disabled = import.meta.env.VITE_WEBSOCKETS_DISABLED ?? 'false'; 4 | if (disabled === 'true') { 5 | window.Echo = null; 6 | } else { 7 | window.Pusher = Pusher; 8 | const env = import.meta.env.VITE_REVERB_ENV ?? 'production'; 9 | const scheme = import.meta.env.VITE_REVERB_SCHEME ?? 'https' 10 | let wsPort = import.meta.env.VITE_REVERB_PORT; 11 | let wssPort = scheme === 'https' ? 443 : wsPort; 12 | if (env !== 'production') { 13 | wssPort = wsPort 14 | } 15 | window.Echo = new Echo({ 16 | broadcaster: 'reverb', 17 | key: import.meta.env.VITE_REVERB_APP_KEY, 18 | wsHost: import.meta.env.VITE_REVERB_HOST, 19 | wsPort, 20 | wssPort, 21 | forceTLS: scheme === 'https', 22 | enabledTransports: ['ws', 'wss'], 23 | }); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /resources/js/vendor/video.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | // VHS (HTTP-streaming) is bundled by default in v7+; no extra import needed 3 | 4 | // VJS styles 5 | import 'video.js/dist/video-js.css'; 6 | 7 | // expose globally so Alpine/x-inline scripts can see it: 8 | window.videojs = videojs; -------------------------------------------------------------------------------- /resources/views/components/qr-modal.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ $label ?? 'QR Code' }} 5 | 6 | 7 | 8 | 9 | {{ $title }} 10 | 11 | 12 |
13 |

14 | {{ $body }} 15 |

16 |
17 |
18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /resources/views/errors/404.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 404 error 5 | 6 | 7 | 8 |
9 |
10 | Logo 11 |

404

12 |

13 | Unable to find the page you are looking for. 14 |

15 |
16 |
17 | 18 | -------------------------------------------------------------------------------- /resources/views/errors/503.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Maintenence mode 5 | 6 | 7 | 8 |
9 |
10 | Logo 11 |

App is currently in maintenence mode, please check back shortly...

12 |
13 |
14 | 15 | -------------------------------------------------------------------------------- /resources/views/filament/pages/dashboard.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/filament/pages/stats.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/forms/components/media-flow-proxy-url.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @php($record = $getRecord()) 3 | @php($urls = \App\Facades\PlaylistUrlFacade::getMediaFlowProxyUrls($record)) 4 | @php($m3uUrl = $urls['m3u']) 5 |
6 | 7 | 12 | 13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /resources/views/infolists/components/series-preview.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @php($record = $getRecord()) 3 | @php($url = $record->url) 4 | @php($proxyUrl = App\Facades\ProxyFacade::getProxyUrlForEpisode($record->id, 'mp4')) 5 | @php($playerId = "episode_{$record->id}_preview") 6 |
7 |
14 | 17 | 18 | 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /resources/views/infolists/components/video-preview.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @php($record = $getRecord()) 3 | @php($url = $record->url_custom ?? $record->url) 4 | @php($proxyUrl = App\Facades\ProxyFacade::getProxyUrlForChannel($record->id, 'mp4')) 5 | @php($playerId = "channel_{$record->id}_preview") 6 |
7 |
14 | 17 | 18 | 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /resources/views/modals/hardware-accel-info.blade.php: -------------------------------------------------------------------------------- 1 |
2 | To use hardware transcoding you need to map /dev/dri to the container. 3 |
4 |
5 | Docker Compose: 6 |
7 |
 8 | devices:
 9 |   - /dev/dri:/dev/dri
10 |     
11 | Docker Run: 12 |
13 | docker run --device /dev/dri:/dev/dri 14 |
15 |
16 | Confirm Access: 17 |
18 | 19 | docker exec -it m3u-editor ls -l /dev/dri 20 | 21 |
22 |
23 | If you see a list of devices like card0, renderD128, etc., then hardware acceleration is set up correctly. 24 |
25 | -------------------------------------------------------------------------------- /resources/views/tables/columns/playlist-auth-name.blade.php: -------------------------------------------------------------------------------- 1 |
2 |

3 | {{ $getRecord()->type() }} 4 |

5 |

6 | {{ $getRecord()->model->name }} 7 |

8 |
9 | -------------------------------------------------------------------------------- /resources/views/vendor/filament-breezy/components/grid-section.blade.php: -------------------------------------------------------------------------------- 1 | @props(['title','description']) 2 | 3 | 4 | 5 |

{{$title}}

6 | 7 |

8 | {{$description}} 9 |

10 |
11 | 12 | 13 | {{ $slot }} 14 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /resources/views/vendor/filament-progress-column/column.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $color = $getColor(); 3 | $barStyles = \Filament\Support\get_color_css_variables( 4 | $color, 5 | shades: [600], 6 | ); 7 | $progress = $getProgress(); 8 | $poll = $getPoll(); 9 | @endphp 10 | 11 |
17 |
18 |
19 |
20 |
21 | 22 | {{ $progress }}% 23 |
24 |
25 | -------------------------------------------------------------------------------- /resources/views/vendor/log-viewer/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/resources/views/vendor/log-viewer/.gitkeep -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 7 | }); 8 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | daily(); 12 | 13 | // Cleanup old/stale job batches 14 | Schedule::command('app:flush-jobs-table') 15 | ->twiceDaily(); 16 | 17 | // Refresh playlists 18 | Schedule::command('app:refresh-playlist') 19 | ->everyFiveMinutes(); 20 | 21 | // Refresh EPG 22 | Schedule::command('app:refresh-epg') 23 | ->everyFiveMinutes(); 24 | 25 | // Prune stale processes 26 | Schedule::command('app:hls-prune') 27 | ->everyFifteenSeconds(); 28 | -------------------------------------------------------------------------------- /screenshots/channel-management.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/screenshots/channel-management.jpg -------------------------------------------------------------------------------- /screenshots/dashboard-light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/screenshots/dashboard-light.jpg -------------------------------------------------------------------------------- /screenshots/dashboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/screenshots/dashboard.jpg -------------------------------------------------------------------------------- /screenshots/group-detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/screenshots/group-detail.jpg -------------------------------------------------------------------------------- /screenshots/group-management.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/screenshots/group-management.jpg -------------------------------------------------------------------------------- /screenshots/playlist-manager-popout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/screenshots/playlist-manager-popout.jpg -------------------------------------------------------------------------------- /screenshots/playlist-manager.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparkison/m3u-editor/8b46253860280fe24b54269d37d24cc1bd63aa93/screenshots/playlist-manager.jpg -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !private/ 3 | !public/ 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /storage/app/private/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /stubs/blueprint/constructor.stub: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a new {{ type }} instance. 3 | */ 4 | public function __construct() 5 | { 6 | {{ body }} 7 | } 8 | -------------------------------------------------------------------------------- /stubs/blueprint/controller.authorize-resource.stub: -------------------------------------------------------------------------------- 1 | public function __construct() 2 | { 3 | $this->authorizeResource({{ modelClass }}::class, '{{ modelVariable }}'); 4 | } 5 | -------------------------------------------------------------------------------- /stubs/blueprint/controller.class.stub: -------------------------------------------------------------------------------- 1 | 42 | */ 43 | public function attachments(): array 44 | { 45 | return []; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /stubs/blueprint/mail.view.stub: -------------------------------------------------------------------------------- 1 | {{-- Template for {{ class }} --}} 2 | -------------------------------------------------------------------------------- /stubs/blueprint/migration.stub: -------------------------------------------------------------------------------- 1 | get('/'); 5 | 6 | $response->assertStatus(200); 7 | }); 8 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | toBeTrue(); 5 | }); 6 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import laravel, { refreshPaths } from "laravel-vite-plugin"; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | laravel({ 7 | input: ["resources/js/app.js"], 8 | refresh: [ 9 | ...refreshPaths, 10 | "app/Filament/**", 11 | "app/Forms/Components/**", 12 | "app/Livewire/**", 13 | "app/Infolists/Components/**", 14 | "app/Providers/Filament/**", 15 | "app/Tables/Columns/**", 16 | ], 17 | }), 18 | ], 19 | build: { 20 | chunkSizeWarningLimit: 1600, 21 | // rollupOptions: { 22 | // output: { 23 | // manualChunks: { 24 | // qrcode: ['easyqrcodejs'], 25 | // videojs: ['video.js'] 26 | // } 27 | // } 28 | // } 29 | } 30 | }); 31 | --------------------------------------------------------------------------------