FastChannels - FAST Channels aggregator/manager

not currently.. your the first person to ask that. I can look into what it'd take.

1 Like

Thank you - hopefully it's easy enough where you can treat each instance as it's own Source like Channels does.

FastChannels 4.7.0

All bug fixes release

VIDAA (Hisense) EPG reliability

  • New installs now default the Vidaa source to disabled
  • It's not looking promising for Vidaa to come back to life so prepping for how I'll handle potentially removing it in future version (maintenance worker will check for missing scrapers daily, and after 7 days, it'll clear out stale channels)

STIRR EPG timestamp parsing

  • Fixed an epoch-timestamp unit-detection bug that was landing program times thousands of years in the future/past on real feeds (OTTera/imagicomm, freelivesports)
  • Added a plausibility check so a malformed timestamp can't silently produce a wrong-but-valid-looking date

Fubo login/credentials

  • Fubo now forces a fresh login on a stale-entitlement 403 instead of misclassifying it
  • Hardened credential-change detection so rotating a password/email is reliably picked up (and a future scraper's secret field can't be silently missed)

Admin UI fixes

  • Fixed the Ch# column showing blank for channels excluded by the default feed's own filters
  • Fixed the category dropdown closing unexpectedly while scrolling its own option list
  • Clarified DRM Bridge channel-count wording on the Feeds page
  • Added a "next scheduled scrape" column and dropped stale beta labeling on the channel-changes report (chDVRuser's report)
  • Fixed the admin "next scrape" estimate so it can't drift out of sync with the actual scheduler logic

Plex / PrismCast

  • Added per-request outcome/latency diagnostics to Plex's guide fetch
  • Sharpened PrismCast's test-setup diagnostic and modal readability, and fixed the modal not closing properly

Maintenance/cleanup jobs

  • Worker now rejects bogus EPG timestamps on ingest and runs a weekly prune + VACUUM
  • Bogus-program prune now also checks start_time, not just end_time (and its cleanup query now batches correctly)

Schema/startup

  • App settings row is now created before schema migrations run (fixes a startup-ordering bug)
  • The inline Gracenote name-match migration is now gated behind a done-flag so it can't silently re-run
3 Likes

Just updated and it looks like I lost everything. And of course I deleted the old image before I realized FastChannels was dead.

Edit: The rebuilt container won't stay connected to the host network. Removed fastchannels_default and added a new bridge connection, which also failed. Deleted the new bridge connection and added the default back. Going to go reboot the entire server.

Your data should still be in a bound directory or Docker Volume. What does the Docker Compose (or docker run command) you used look like?

services:
  fastchannels:
    image: ghcr.io/kineticman/fastchannels:latest
    container_name: fastchannels
    restart: unless-stopped
    ports:
      - "5523:5523"
    volumes:
      - db_data:/data

volumes:
  db_data:

So your data is in a Docker Volume called fastchannels_db_data. Be sure to not delete that.

2 Likes

I'm back in business. Changed the pull from latest to v4.6.1. Came right up.

fastchannels:
image: ghcr.io/kineticman/fastchannels:v4.6.1

Didn't have any luck starting 4.7.1 either. Here's the startup log if its of any use:

🚀 Starting FastChannels...
✅ Redis started
⏳ Waiting for Redis...
✅ Redis ready
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
    self.dialect.do_execute(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute
    cursor.execute(statement, parameters)
sqlite3.OperationalError: no such column: app_settings.prismcast_url

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/app/app/__init__.py", line 92, in create_app
    AppSettings.get()
  File "/app/app/models.py", line 455, in get
    row = cls.query.get(1)
          ^^^^^^^^^^^^^^^^
  File "<string>", line 2, in get
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/util/deprecations.py", line 386, in warned
    return fn(*args, **kwargs)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 1127, in get
    return self._get_impl(ident, loading.load_on_pk_identity)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 1136, in _get_impl
    return self.session._get_impl(
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 3881, in _get_impl
    return db_load_fn(
           ^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/loading.py", line 695, in load_on_pk_identity
    session.execute(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2373, in execute
    return self._execute_internal(
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2271, in _execute_internal
    result: Result[Any] = compile_state_cls.orm_execute_statement(
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/context.py", line 306, in orm_execute_statement
    result = conn.execute(
             ^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1421, in execute
    return meth(
           ^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/sql/elements.py", line 526, in _execute_on_connection
    return connection._execute_clauseelement(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1643, in _execute_clauseelement
    ret = self._execute_context(
          ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
    return self._exec_single_context(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
    self._handle_dbapi_exception(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 2365, in _handle_dbapi_exception
    raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
    self.dialect.do_execute(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: app_settings.prismcast_url
[SQL: SELECT app_settings.id AS app_settings_id, app_settings.global_chnum_start AS app_settings_global_chnum_start, app_settings.channels_dvr_url AS app_settings_channels_dvr_url, app_settings.public_base_url AS app_settings_public_base_url, app_settings.timezone_name AS app_settings_timezone_name, app_settings.auto_allow_new_channels AS app_settings_auto_allow_new_channels, app_settings.gracenote_auto_fill AS app_settings_gracenote_auto_fill, app_settings.dvr_epg_auto_refresh AS app_settings_dvr_epg_auto_refresh, app_settings.image_proxy_enabled AS app_settings_image_proxy_enabled, app_settings.gracenote_map_url AS app_settings_gracenote_map_url, app_settings.gracenote_contribution_url AS app_settings_gracenote_contribution_url, app_settings.last_contribution_at AS app_settings_last_contribution_at, app_settings.prismcast_url AS app_settings_prismcast_url, app_settings.prismcast_inner_url AS app_settings_prismcast_inner_url, app_settings.drm_bridge_enabled AS app_settings_drm_bridge_enabled 
FROM app_settings 
WHERE app_settings.id = ?]
[parameters: (1,)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)

well fiddle sticks... in attempt to fix one bug, i introduced another. it's just a timing issue on my Schema/startup fix.

gimme a few and i'll push a hotfix. i think Belgium gonna win anyway.

Sorry guys I should have caught this one.. just pushed 4.7.1 (latest). Fixes that boot crash.

1 Like

Nice, 4.7.1 is looking good! Thanks for such a quick fix

🚀 Starting FastChannels...
✅ Redis started
⏳ Waiting for Redis...
✅ Redis ready
✅ DB ready
2026-07-07 01:57:44,554 INFO     app.worker: Seeded 25 sources
✅ Sources seeded
✅ Orphaned sources checked
⏳ Waiting for outbound network and DNS...
network check failed: [Errno 101] Network is unreachable
✅ Network ready
✅ Worker roles started (scheduler, scraper, fast, maintenance)
✅ Starting gunicorn on port 5523
2026-07-07 01:58:05,945 INFO     __main__: FastChannels worker v4.7.1 starting
2026-07-07 01:58:05,951 INFO     __main__: Fast worker listening on queue: fast
2026-07-07 01:58:05,963 INFO     __main__: FastChannels worker v4.7.1 starting
2026-07-07 01:58:05,966 INFO     __main__: Maintenance worker listening on queue: maintenance
2026-07-07 01:58:06,092 INFO     __main__: FastChannels worker v4.7.1 starting
2026-07-07 01:58:06,095 INFO     __main__: Scraper worker listening on queue: scraper
2026-07-07 01:58:06,177 INFO     __main__: FastChannels worker v4.7.1 starting
2026-07-07 01:58:06,189 INFO     __main__: Scheduler started — checking sources every 60s
2026-07-07 01:58:06,221 INFO     __main__: Startup summary — enabled_sources=12 total_sources=25 enabled_feeds=2
2026-07-07 01:58:06,224 INFO     __main__: [xml-cache] enqueued refresh job
[2026-07-07 01:58:06 +0000] [1] [INFO] Starting gunicorn 23.0.0
2026-07-07 01:58:06,487 INFO     gunicorn.error: Listening at: http://0.0.0.0:5523 (1)
1 Like

Just noticed this during 4.7.1 startup:

⏳ Waiting for outbound network and DNS...
network check failed: [Errno 101] Network is unreachable

Comparing with 4.6.1 log:

🚀 Starting FastChannels...
✅ Redis started
⏳ Waiting for Redis...
✅ Redis ready
✅ DB ready
2026-07-07 02:06:56,845 INFO     app.worker: Seeded 25 sources
✅ Sources seeded
⏳ Waiting for outbound network and DNS...
✅ Network ready
✅ Worker roles started (scheduler, scraper, fast, maintenance)
✅ Starting gunicorn on port 5523
2026-07-07 02:07:01,528 INFO     __main__: FastChannels worker v4.6.1 starting

Is that an issue? I dont think scraping works in 4.7.1 with that network error. But not sure if this error is unique to my situation. I use fast-channels with gluetun vpn, could that be causing the network failure? But it does work with 4.6.1.

I’ll double check in morning. I tested a fresh dummy install and worked fine for me.

im getting http 500 errors when i try to save feed changes.

this was in the log

2026-07-06 22:42:32,130 INFO     gunicorn.access: 192.168.37.89 "GET /api/stats?source=adultswim&source=ballysports&source=distro&source=freelivesports&source=hallmark&source=lg-channels&source=stirr&source=tubi&source=vidaa&source=whale&source=xumo&language=en HTTP/1.1" 200 1121 0s
2026-07-06 22:43:05,832 ERROR    app: Exception on /api/feeds/8 [PATCH]
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
    self.dialect.do_execute(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute
    cursor.execute(statement, parameters)
sqlite3.OperationalError: database is locked

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 1511, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 919, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 917, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/flask/app.py", line 902, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/app/routes/feeds_api.py", line 220, in update_feed
    from ..worker import _refresh_auto_channel_numbers
  File "/app/app/worker.py", line 69, in <module>
    flask_app = create_app()
                ^^^^^^^^^^^^
  File "/app/app/__init__.py", line 95, in create_app
    db.session.execute(_text("INSERT OR IGNORE INTO app_settings (id) VALUES (1)"))
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/scoping.py", line 764, in execute
    return self._proxied.execute(
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2373, in execute
    return self._execute_internal(
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2280, in _execute_internal
    result = conn.execute(
             ^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1421, in execute
    return meth(
           ^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/sql/elements.py", line 526, in _execute_on_connection
    return connection._execute_clauseelement(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1643, in _execute_clauseelement
    ret = self._execute_context(
          ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
    return self._exec_single_context(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
    self._handle_dbapi_exception(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 2365, in _handle_dbapi_exception
    raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
    self.dialect.do_execute(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) database is locked
[SQL: INSERT OR IGNORE INTO app_settings (id) VALUES (1)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
2026-07-06 22:43:05,838 INFO     gunicorn.access: 192.168.37.89 "PATCH /api/feeds/8 HTTP/1.1" 500 265 30s

I just restarted the container again and network looks good now. Must have been me/my setup at the time.

⏳ Waiting for outbound network and DNS...
✅ Network ready
✅ Worker roles started (scheduler, scraper, fast, maintenance)
✅ Starting gunicorn on port 5523
2026-07-07 02:26:18,757 INFO     __main__: FastChannels worker v4.7.1 starting

v4.7.1 also solved my issue with missing channels from pluto tv, thank you!

Thanks; that means I don't have to worry too much about checking recording schedule before deciding when to update. Only issue, I guess, is if an update (or more likely a scrape) would remove a channel that I thought I had.

Assuming this was the item I raised the other day? If so, did the upgrade to 4.7.1 but still seeing the same issue:

let me come back to this one.. i'm not having the best of luck this week...

OK Sorry yet again - the hotfix I pushed last night only half fixed my original screwup. It only fixed the startup issue but didn't fix the DB lock issue. I was in too much of a rush...

Pushing 4.7.3 right now -- DB lock issue resolved. Should be stable??

3 Likes