FastChannels - FAST Channels aggregator/manager

AUTO means it uses the Gracenote ID from the upstream data ie Pluto (if it exists). If you select OFF/Manual, it just uses whatever you type in there (or leave blank if you want to use scraper EPG). There is a Community Map that is somewhat vetted that will give you a few more Gracenote IDs. It's a work in progress....

● v2.9.0

  • Admin themes — choose between Dark (new default), Light, or :purple_square: in the admin panel
  • Duplicate channel resolution — new tools to review and resolve soft duplicate channels, with smarter matching rules that factor in EPG coverage when picking the winner
  • EPG coverage filter — filter channels in admin by how much guide data they have
  • Channel age filter — filter admin channels by how recently they were added
  • Gracenote channel numbers are now sticky — Gracenote-backed channels hold their assigned numbers just like standard channels
  • Distro TV multi-region support — picks up channels across all available Distro regions

Bug fixes:

  • Stream audit now correctly skips inactive channels (but still catches them recovering)
  • Gracenote ID column tightened up in the community map
3 Likes

@KineticMan Thanks for the speed of development on this project.. I'm finding that with the built-in player, I can basically use the web page for most of my daily viewing...
With this said, I'm not sure how you set up the player, I see hls.js when a channel plays... but I have a few that are not capable of being played via the built-in, but they do play in VLC...
Notably the channels with 4k in their names...
I was wondering if you could try to see if the shaka player (GitHub - shaka-project/shaka-player: JavaScript player library / DASH & HLS client / MSE-EME player · GitHub) could be an option for the built-in player? Maybe in a separate dev release so it doesn't affect the main release?
Who knows, you may have already tried it and it doesn't work, but I just thought I would put it out there...
2 of the channels are Clarity 4k and Love Nature 4K

I didn’t put much time into the web-player — it was more so for debugging streams for previews.

That being said, I’m not opposed to looking into it. I’m not familiar with this software- what advantages would it have vs a link to open the stream in VLC? I just don’t want to add a whole layer of maintainence to the project - I hope you understand.

@KineticMan Totally understand your position...
The reason I ask: as I said, most of the channels actually play with the in-player except for a few, namely the 2 I listed... from my previous posts, you know I love the nature/animal channels...
Anyway, when I click the play in external player, it doesn't open directly in VLC, even though I set it as the default in Windows. Instead, it opens in a new tab in Brave but it doesn't play, so I have to copy the link and then paste it into VLC...
It's something I can live with, so no need to put additional work layers in... I was just hoping it would be a "replace this player with this other player" replacement kind of thing...
I'm not a dev so I have no ideas of the work that is necessary...
This is like a replacement for iptvnator for me...since it does so much more...
Again, thanks for this project...

See

Just updated to v2.9.0 and scheduled scrapes are failing
I was looking at channels added in the last 3 days and disabling the new ones I don't want.

log
2026-04-06 15:02:08,643 INFO     __main__: [scheduler] Enqueued samsung (interval=60m, age=61m)
2026-04-06 15:02:08,985 INFO     app.worker: [samsung] Scrape job started
2026-04-06 15:02:08,987 INFO     app.worker: [samsung] scraper init starting
2026-04-06 15:02:08,989 INFO     app.worker: [samsung] scraper init complete
2026-04-06 15:02:08,990 INFO     app.worker: [samsung] bootstrap starting
2026-04-06 15:02:08,990 INFO     app.worker: [samsung] bootstrap complete
2026-04-06 15:02:08,992 INFO     app.worker: [samsung] channel fetch starting
2026-04-06 15:02:08,993 INFO     app.scrapers.samsung: [samsung] fetching channel list for regions=['us']
2026-04-06 15:02:09,845 INFO     app.scrapers.samsung: [samsung] 489 channels fetched (region=us)
2026-04-06 15:02:09,846 INFO     app.scrapers.samsung: [samsung] 489 total channels across 1 region(s)
2026-04-06 15:02:09,851 INFO     app.worker: [samsung] channel fetch complete
2026-04-06 15:02:09,852 INFO     app.worker: [samsung] EPG fetch starting
2026-04-06 15:02:10,548 INFO     app.scrapers.samsung: [samsung] fetching EPG for regions=['us'] (489 channels)
2026-04-06 15:02:11,740 INFO     app.scrapers.samsung: [samsung] 4100 programs parsed (region=us)
2026-04-06 15:02:11,740 INFO     app.scrapers.samsung: [samsung] 4100 total programs across 1 region(s)
2026-04-06 15:02:11,753 INFO     app.worker: [samsung] EPG fetch complete
2026-04-06 15:02:19,331 INFO     app.tvtv_cache: [tvtv-cache] USA-GNSTR-X day+0: 94 stations in 5 batches
2026-04-06 15:02:37,637 ERROR    app: Exception on /api/channels/2925 [PATCH]
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1967, 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/api.py", line 818, in update_channel
    db.session.commit()
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/scoping.py", line 597, in commit
    return self._proxied.commit()
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2030, in commit
    trans.commit(_to_root=True)
  File "<string>", line 2, in commit
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go
    ret_value = fn(self, *arg, **kw)
                ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 1311, in commit
    self._prepare_impl()
  File "<string>", line 2, in _prepare_impl
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go
    ret_value = fn(self, *arg, **kw)
                ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 1286, in _prepare_impl
    self.session.flush()
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 4331, in flush
    self._flush(objects)
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 4466, in _flush
    with util.safe_reraise():
         ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/util/langhelpers.py", line 121, in __exit__
    raise exc_value.with_traceback(exc_tb)
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 4427, in _flush
    flush_context.execute()
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/unitofwork.py", line 466, in execute
    rec.execute(self)
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/unitofwork.py", line 642, in execute
    util.preloaded.orm_persistence.save_obj(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/persistence.py", line 85, in save_obj
    _emit_update_statements(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/persistence.py", line 912, in _emit_update_statements
    c = connection.execute(
        ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1419, in execute
    return meth(
           ^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/sql/elements.py", line 527, in _execute_on_connection
    return connection._execute_clauseelement(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1641, in _execute_clauseelement
    ret = self._execute_context(
          ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1846, in _execute_context
    return self._exec_single_context(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1986, in _exec_single_context
    self._handle_dbapi_exception(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 2363, 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 1967, 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: UPDATE channels SET is_enabled=?, updated_at=? WHERE channels.id = ?]
[parameters: (0, '2026-04-06 22:02:07.605784', 2925)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
2026-04-06 15:02:41,997 WARNING  app.worker: [samsung] DB locked (attempt 1/3), retrying in 5s
2026-04-06 15:02:42,533 INFO     app.tvtv_cache: [tvtv-cache] USA-GNSTR-X day+1: 94 stations in 5 batches
2026-04-06 15:03:00,709 INFO     app.tvtv_cache: [tvtv-cache] USA-GNSTR-X day+2: 94 stations in 5 batches
2026-04-06 15:03:14,067 INFO     app.tvtv_cache: [tvtv-cache] USA-SAMSUNG-X day+0: 51 stations in 3 batches
2026-04-06 15:03:17,169 WARNING  app.worker: [samsung] DB locked (attempt 2/3), retrying in 10s
2026-04-06 15:03:22,296 INFO     app.tvtv_cache: [tvtv-cache] USA-SAMSUNG-X day+1: 51 stations in 3 batches
2026-04-06 15:03:31,980 INFO     app.tvtv_cache: [tvtv-cache] USA-SAMSUNG-X day+2: 51 stations in 3 batches
2026-04-06 15:03:42,148 INFO     app.tvtv_cache: [tvtv-cache] USA-AMZPV-X day+0: 26 stations in 2 batches
2026-04-06 15:03:48,743 INFO     app.tvtv_cache: [tvtv-cache] USA-AMZPV-X day+1: 26 stations in 2 batches
2026-04-06 15:03:52,504 ERROR    app: Exception on /api/channels/2925 [PATCH]
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1967, 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/api.py", line 818, in update_channel
    db.session.commit()
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/scoping.py", line 597, in commit
    return self._proxied.commit()
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2030, in commit
    trans.commit(_to_root=True)
  File "<string>", line 2, in commit
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go
    ret_value = fn(self, *arg, **kw)
                ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 1311, in commit
    self._prepare_impl()
  File "<string>", line 2, in _prepare_impl
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go
    ret_value = fn(self, *arg, **kw)
                ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 1286, in _prepare_impl
    self.session.flush()
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 4331, in flush
    self._flush(objects)
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 4466, in _flush
    with util.safe_reraise():
         ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/util/langhelpers.py", line 121, in __exit__
    raise exc_value.with_traceback(exc_tb)
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 4427, in _flush
    flush_context.execute()
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/unitofwork.py", line 466, in execute
    rec.execute(self)
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/unitofwork.py", line 642, in execute
    util.preloaded.orm_persistence.save_obj(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/persistence.py", line 85, in save_obj
    _emit_update_statements(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/persistence.py", line 912, in _emit_update_statements
    c = connection.execute(
        ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1419, in execute
    return meth(
           ^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/sql/elements.py", line 527, in _execute_on_connection
    return connection._execute_clauseelement(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1641, in _execute_clauseelement
    ret = self._execute_context(
          ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1846, in _execute_context
    return self._exec_single_context(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1986, in _exec_single_context
    self._handle_dbapi_exception(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 2363, 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 1967, 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: UPDATE channels SET is_enabled=?, updated_at=? WHERE channels.id = ?]
[parameters: (0, '2026-04-06 22:03:22.474044', 2925)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
2026-04-06 15:03:55,454 INFO     app.tvtv_cache: [tvtv-cache] USA-AMZPV-X day+2: 26 stations in 2 batches
2026-04-06 15:03:57,317 ERROR    app.worker: [samsung] Scrape failed after 108.3s
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1936, in _exec_single_context
    self.dialect.do_executemany(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 949, in do_executemany
    cursor.executemany(statement, parameters)
sqlite3.OperationalError: database is locked
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "/app/app/worker.py", line 328, in run_scraper
    _upsert_channels(source, channels, _gracenote_auto_fill)
  File "/app/app/worker.py", line 1450, in _upsert_channels
    _refresh_auto_channel_numbers()
  File "/app/app/worker.py", line 1307, in _refresh_auto_channel_numbers
    .all()
     ^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 2704, in all
    return self._iter().all()  # type: ignore
           ^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/query.py", line 2857, in _iter
    result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
                                                  ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2351, in execute
    return self._execute_internal(
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2228, in _execute_internal
    ) = compile_state_cls.orm_pre_session_exec(
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/context.py", line 577, in orm_pre_session_exec
    session._autoflush()
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 3051, in _autoflush
    raise e.with_traceback(sys.exc_info()[2])
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 3040, in _autoflush
    self.flush()
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 4331, in flush
    self._flush(objects)
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 4466, in _flush
    with util.safe_reraise():
         ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/util/langhelpers.py", line 121, in __exit__
    raise exc_value.with_traceback(exc_tb)
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 4427, in _flush
    flush_context.execute()
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/unitofwork.py", line 466, in execute
    rec.execute(self)
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/unitofwork.py", line 642, in execute
    util.preloaded.orm_persistence.save_obj(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/persistence.py", line 85, in save_obj
    _emit_update_statements(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/persistence.py", line 912, in _emit_update_statements
    c = connection.execute(
        ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1419, in execute
    return meth(
           ^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/sql/elements.py", line 527, in _execute_on_connection
    return connection._execute_clauseelement(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1641, in _execute_clauseelement
    ret = self._execute_context(
          ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1846, in _execute_context
    return self._exec_single_context(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1986, in _exec_single_context
    self._handle_dbapi_exception(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 2363, 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 1936, in _exec_single_context
    self.dialect.do_executemany(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/default.py", line 949, in do_executemany
    cursor.executemany(statement, parameters)
sqlalchemy.exc.OperationalError: (raised as a result of Query-invoked autoflush; consider using a session.no_autoflush block if this flush is occurring prematurely)
(sqlite3.OperationalError) database is locked
[SQL: UPDATE channels SET last_seen_at=?, updated_at=? WHERE channels.id = ?]
[parameters: [('2026-04-06 22:03:27.205683', '2026-04-06 22:03:27.272022', 1317), ('2026-04-06 22:03:27.205683', '2026-04-06 22:03:27.272033', 1318), ('2026-04-06 22:03:27.205683', '2026-04-06 22:03:27.272035', 1319), ('2026-04-06 22:03:27.205683', '2026-04-06 22:03:27.272037', 1320), ('2026-04-06 22:03:27.205683', '2026-04-06 22:03:27.272040', 1321), ('2026-04-06 22:03:27.205683', '2026-04-06 22:03:27.272042', 1322), ('2026-04-06 22:03:27.205683', '2026-04-06 22:03:27.272044', 1323), ('2026-04-06 22:03:27.205683', '2026-04-06 22:03:27.272046', 1324)]]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
2026-04-06 15:04:02,037 INFO     app.tvtv_cache: [tvtv-cache] USA-TUBISTR-X day+0: 14 stations in 1 batches
2026-04-06 15:04:05,763 INFO     app.tvtv_cache: [tvtv-cache] USA-TUBISTR-X day+1: 14 stations in 1 batches
2026-04-06 15:04:09,380 INFO     app.tvtv_cache: [tvtv-cache] USA-TUBISTR-X day+2: 14 stations in 1 batches
2026-04-06 15:04:13,022 INFO     app.tvtv_cache: [tvtv-cache] USA-VIZIO-X day+0: 4 stations in 1 batches
2026-04-06 15:04:16,638 INFO     app.tvtv_cache: [tvtv-cache] USA-VIZIO-X day+1: 4 stations in 1 batches
2026-04-06 15:04:20,191 INFO     app.tvtv_cache: [tvtv-cache] USA-VIZIO-X day+2: 4 stations in 1 batches
2026-04-06 15:04:20,711 ERROR    app: Exception on /api/channels/2924 [PATCH]
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1967, 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/api.py", line 818, in update_channel
    db.session.commit()
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/scoping.py", line 597, in commit
    return self._proxied.commit()
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 2030, in commit
    trans.commit(_to_root=True)
  File "<string>", line 2, in commit
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go
    ret_value = fn(self, *arg, **kw)
                ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 1311, in commit
    self._prepare_impl()
  File "<string>", line 2, in _prepare_impl
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go
    ret_value = fn(self, *arg, **kw)
                ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 1286, in _prepare_impl
    self.session.flush()
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 4331, in flush
    self._flush(objects)
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 4466, in _flush
    with util.safe_reraise():
         ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/util/langhelpers.py", line 121, in __exit__
    raise exc_value.with_traceback(exc_tb)
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/session.py", line 4427, in _flush
    flush_context.execute()
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/unitofwork.py", line 466, in execute
    rec.execute(self)
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/unitofwork.py", line 642, in execute
    util.preloaded.orm_persistence.save_obj(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/persistence.py", line 85, in save_obj
    _emit_update_statements(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/orm/persistence.py", line 912, in _emit_update_statements
    c = connection.execute(
        ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1419, in execute
    return meth(
           ^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/sql/elements.py", line 527, in _execute_on_connection
    return connection._execute_clauseelement(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1641, in _execute_clauseelement
    ret = self._execute_context(
          ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1846, in _execute_context
    return self._exec_single_context(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 1986, in _exec_single_context
    self._handle_dbapi_exception(
  File "/usr/local/lib/python3.12/site-packages/sqlalchemy/engine/base.py", line 2363, 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 1967, 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: UPDATE channels SET is_enabled=?, updated_at=? WHERE channels.id = ?]
[parameters: (0, '2026-04-06 22:03:50.677881', 2924)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
2026-04-06 15:04:23,763 INFO     app.tvtv_cache: [tvtv-cache] USA-PLUTOTV-X day+0: 13 stations in 1 batches
2026-04-06 15:04:27,428 WARNING  app.worker: [samsung] Could not persist last_error to DB
2026-04-06 15:04:27,686 INFO     app.tvtv_cache: [tvtv-cache] USA-PLUTOTV-X day+1: 13 stations in 1 batches
2026-04-06 15:04:31,591 INFO     app.tvtv_cache: [tvtv-cache] USA-PLUTOTV-X day+2: 13 stations in 1 batches
2026-04-06 15:04:37,205 INFO     app.tvtv_cache: [tvtv-cache] refresh complete: {'lineups_fetched': 8, 'days': 3, 'batches': 81, 'rows_inserted': 41217, 'rows_deleted': 10239, 'errors': 0, 'elapsed_s': 274.6, 'dry_run': False}
2026-04-06 15:04:37,209 INFO     app.worker: [tvtv-cache] refresh complete: {'lineups_fetched': 8, 'days': 3, 'batches': 81, 'rows_inserted': 41217, 'rows_deleted': 10239, 'errors': 0, 'elapsed_s': 274.6, 'dry_run': False}
2026-04-06 15:05:11,857 INFO     app.routes.tasks: Enqueued XML artifact refresh
2026-04-06 15:05:55,646 INFO     app.routes.tasks: Enqueued XML artifact refresh

Restarted the container and it finished the scheduled scrape.

looks like the TvTV scraper (the API that gets the Gracenote helper data) took way too long to complete and locked up the system. I'm going to add a defensive step in there to prevent that.

edit - and i'm moving that job to middle of night. it scrapes 3 days ahead anyway so no need to run in middle of day/evening. good catch tnx

1 Like

Even that would be nice as I'm still looking for summaries of what channels are about.

@KineticMan When you get the chance, at your leisure, no rush...
could you please correct/merge this category?
It's under a separate "Quality Eats Presented by Capital One" category... but they should be under Food.

@KineticMan Question on additions of new channels...Hope this comes across in a sane way...
Let's say I have 2 Feeds, "Animals and Nature" and "Travel and Food".
I have the appropriate categories added to those Feeds.
During a future scrape, some new channels gets added (like you just did for the LocalNow locations)... and it has some channels with either animals or travel...
Since you will be adding them to the corresponding categories, does this mean if you add something to the Travel category, it will auto update my Travel Feed?
Or will I need to go back and refresh my Feeds?

What's nice about the new version is you can see what channels have been added recently.

Because new channels scraped will be enabled by default and will get into your feeds based on category (or whatever your feed selection is), you will have to manicure them occasionally.

2 Likes

Hmmm.. OK, thanks for the tip...
I had to re-look at the changes for the last update to see I have to toggle it in the Channels tab...
OK, I see you just pasted the screenshot...
Got it now...
However, I'm not sure what you mean by "you will have to manicure them occasionally"...
So I will have to edit the feed and just save it again?

You edit the new channels.
What I just did since he added this is look at new channels that were scraped in the last 3 days.
I then decided whether I wanted them enabled and for those I did I went through the normal procedure (my workflow) of adding a channel. For those I don't want I disable the channel.

Makes it a lot easier to catch new channels than the old way (without FastChannels).

1 Like

well, that's weird...... no clue how that happened. will fix in next version

1 Like

@KineticMan Please disregard my request for the Shaka player, unless you still want to look at it at some point...
I've found a Chrome add-on VideoPlayer, that allows me to open the non-playing streams via FastChannels in a new tab... So when I click play and I get the error

Browser preview unavailable for this source (CORS restriction on stream CDN). Use "Open Externally" to play outside the browser preview.

I click the Open Externally and Brave opens a new tab and actually plays it... which it wasn't before...
So I'm good to go now...

Ok.

I am still adding an open in VLC button (or similar wording). It’ll create a one line M3U that will download in your browser then your OS can auto-open in whatever external video player you want.

2 Likes

Channels DVR has an Open in VLC integration that requires a helper app to add the registered protocol vlc:// and pass the stream to VLC (Windows only)

Also see Play in VLC integration now broken? - #7 by chDVRuser

I'm getting conflicting results for Distro TV.
The scrape finds 309 channels:

Last audit: 304 ok · 2 rate-limited · 1 errors · 4/6/2026, 11:06:33 PM

I checked several Distro channels and they say "Live 1920x1080". However, they will not Preview either internally or externally. For external viewing, I'm getting a message like below (I truncated the extremely long URL). Likewise, attempts to tune in CDVR get a 404.

This d35j504z0x2vu2.cloudfront.net page can’t be found

No webpage was found for the web address:
https://d35j504z0x2vu2.cloudfront.net/v1/master/0bc8e8376bd8417a1b6761138aa41c26c7309312/drive-plus-speed/manifest.m3u8?

@autryld This is somewhat a similar issue I reported earlier today...
see

and the follow-up a few posts after...
but the channel I wanted to watch was viewable via VLC... I found a fix by installing an add-on to my Brave browser and I'm able to watch the channel via the "Open Externally" where the channel opens and play in a separate tab....
I just checked the 4 Distro Speed channels:

and they won't play via Microsoft Edge but they will play in Brave...

So IF you want to try it, until @KineticMan gets a fix in, and you're running a Chrome browser, search the extension store for "VideoPlayer MPD/M3U8/IPTV/EPG"
If not, no worries... just sending a suggestion