├── .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 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/resources/views/errors/404.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 404 error
5 |
6 |
7 |
8 |
9 |
10 |
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 |
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 |
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 |
--------------------------------------------------------------------------------