If the Osprey is looking for a lower level input, closer to a real, remote button press, would scrcpy register more?
Hi all!
I have been working on setting up a few channels using the HDMI Encoder Native Apps.
I have already successfully added my local CBS channel. Next up: PBS.
If I import the PBS channel list json, using the "Delete" option, will it only delete existing channels from the same input source? (PBS app) I'd hate to have it dump everything I have worked so hard to set up already.
Are you talking about ABDTuner's "Delete" option?

If so, it will delete ALL existing stations, so don't do that!
Or are you talking about OliveTin's "NATIVE_REMOVE" option?
That one only deletes the stations associated with that source app, so is more like a scalpel.
In either case, I'm not sure why you are looking to delete at all? If you loaded up CBS and now you want PBS, it is just an additive function. They don't overlap in any of the settings, so you can just import them in individually without any worries.
My question was triggered by this issue which occurred while importing the PBS json. It skips the main PBS Local channel, identifying it as a duplicate. No such channel currently exists in my ADBT lineup.

I thought that it might be leftover Data from a previous attempt to add PBS. I had hoped to use "Delete" to remove the offending duplicate. Good thing I didn't do that. I'm glad to know of the "Native Remove" Olivetin function. Somehow, I never heard of that action.
Do you have any idea why I received the "skipped duplicate" message, and how I can successfully import both PBS channels in your PBS json?
This is exactly what is happening with PBS. Only the second channel "NHK World" is loaded.
I must have missed this conversation. So much has been changing lately, that it's hard for my aging mind to keep up... my
=

I just pushed a new development build (20260728-1). One change, package name and alternate package name are now considered when determining if a "url" is a duplicate.
This should fix those channel list imports with duplicate values in the URL field.
This was my experience, too. I used the OliveTin "Create Native ADBTuner Channels + CDVR Custom Channels" action, including the "Native Remove" option for PBS, and it loaded both channels from the PBS json.
Thanks for the help!
I believe that OliveTin was using ADBTuner's API to add the channels one at a time which bypassed the validation (that was a bug on my end). The validation is now applied there as well but shouldn't be an issue since the package_name fields are also taken into consideration.
I'll give it a whirl later tonight to see since I got ESPN Unlimited, the true test can begin 
I'm trying to do a net new install to test this against, but keep running into this:
2026-07-28 15:00:12.570 | /app/.venv/bin/python: can't open file '/app/migrate.py': [Errno 2] No such file or directory
2026-07-28 15:00:12.985 | 2026/07/28 15:00:12 Starting proxy server with MPEG-TS null packet support
2026-07-28 15:00:12.985 | 2026/07/28 15:00:12 Loaded routes from file:
2026-07-28 15:00:12.985 |
2026-07-28 15:00:23.437 | 2026/07/28 15:00:23 [PROXY] Proxy error: dial tcp 127.0.0.1:5593: connect: connection refused
2026-07-28 15:00:31.578 | INFO: Started server process [10]
2026-07-28 15:00:31.579 | INFO: Waiting for application startup.
2026-07-28 15:00:31.587 | 2026-07-28 15:00:31.583 - server - ADBTuner 20260728-1
2026-07-28 15:00:31.587 | 2026-07-28 15:00:31.584 - schema_verification - Verifying database schema (/app/.config/config.db).
2026-07-28 15:00:31.810 | 2026-07-28 15:00:31.809 - server - Database schema verification was successful.
2026-07-28 15:00:32.158 | ERROR: Traceback (most recent call last):
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
2026-07-28 15:00:32.158 | self.dialect.do_execute(
2026-07-28 15:00:32.158 | ~~~~~~~~~~~~~~~~~~~~~~~^
2026-07-28 15:00:32.158 | cursor, str_statement, effective_parameters, context
2026-07-28 15:00:32.158 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.158 | )
2026-07-28 15:00:32.158 | ^
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute
2026-07-28 15:00:32.158 | cursor.execute(statement, parameters)
2026-07-28 15:00:32.158 | ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.158 | sqlite3.OperationalError: no such table: app_settings
2026-07-28 15:00:32.158 |
2026-07-28 15:00:32.158 | The above exception was the direct cause of the following exception:
2026-07-28 15:00:32.158 |
2026-07-28 15:00:32.158 | Traceback (most recent call last):
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/starlette/routing.py", line 638, in lifespan
2026-07-28 15:00:32.158 | async with self.lifespan_context(app) as maybe_state:
2026-07-28 15:00:32.158 | ~~~~~~~~~~~~~~~~~~~~~^^^^^
2026-07-28 15:00:32.158 | File "/usr/local/lib/python3.14/contextlib.py", line 214, in __aenter__
2026-07-28 15:00:32.158 | return await anext(self.gen)
2026-07-28 15:00:32.158 | ^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/fastapi/routing.py", line 216, in merged_lifespan
2026-07-28 15:00:32.158 | async with original_context(app) as maybe_original_state:
2026-07-28 15:00:32.158 | ~~~~~~~~~~~~~~~~^^^^^
2026-07-28 15:00:32.158 | File "/usr/local/lib/python3.14/contextlib.py", line 214, in __aenter__
2026-07-28 15:00:32.158 | return await anext(self.gen)
2026-07-28 15:00:32.158 | ^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/fastapi/routing.py", line 216, in merged_lifespan
2026-07-28 15:00:32.158 | async with original_context(app) as maybe_original_state:
2026-07-28 15:00:32.158 | ~~~~~~~~~~~~~~~~^^^^^
2026-07-28 15:00:32.158 | File "/usr/local/lib/python3.14/contextlib.py", line 214, in __aenter__
2026-07-28 15:00:32.158 | return await anext(self.gen)
2026-07-28 15:00:32.158 | ^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/fastapi/routing.py", line 216, in merged_lifespan
2026-07-28 15:00:32.158 | async with original_context(app) as maybe_original_state:
2026-07-28 15:00:32.158 | ~~~~~~~~~~~~~~~~^^^^^
2026-07-28 15:00:32.158 | File "/usr/local/lib/python3.14/contextlib.py", line 214, in __aenter__
2026-07-28 15:00:32.158 | return await anext(self.gen)
2026-07-28 15:00:32.158 | ^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.158 | File "/app/app/main.py", line 261, in lifespan
2026-07-28 15:00:32.158 | migrate_file_configurations()
2026-07-28 15:00:32.158 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
2026-07-28 15:00:32.158 | File "/app/app/dependencies.py", line 532, in migrate_file_configurations
2026-07-28 15:00:32.158 | .first()
2026-07-28 15:00:32.158 | ~~~~~^^
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/orm/query.py", line 2766, in first
2026-07-28 15:00:32.158 | return self.limit(1)._iter().first() # type: ignore
2026-07-28 15:00:32.158 | ~~~~~~~~~~~~~~~~~~~^^
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/orm/query.py", line 2864, in _iter
2026-07-28 15:00:32.158 | result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
2026-07-28 15:00:32.158 | ~~~~~~~~~~~~~~~~~~~~^
2026-07-28 15:00:32.158 | statement,
2026-07-28 15:00:32.158 | ^^^^^^^^^^
2026-07-28 15:00:32.158 | params,
2026-07-28 15:00:32.158 | ^^^^^^^
2026-07-28 15:00:32.158 | execution_options={"_sa_orm_load_options": self.load_options},
2026-07-28 15:00:32.158 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.158 | )
2026-07-28 15:00:32.158 | ^
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 2372, in execute
2026-07-28 15:00:32.158 | return self._execute_internal(
2026-07-28 15:00:32.158 | ~~~~~~~~~~~~~~~~~~~~~~^
2026-07-28 15:00:32.158 | statement,
2026-07-28 15:00:32.158 | ^^^^^^^^^^
2026-07-28 15:00:32.158 | ...<4 lines>...
2026-07-28 15:00:32.158 | _add_event=_add_event,
2026-07-28 15:00:32.158 | ^^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.158 | )
2026-07-28 15:00:32.158 | ^
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 2270, in _execute_internal
2026-07-28 15:00:32.158 | result: Result[Any] = compile_state_cls.orm_execute_statement(
2026-07-28 15:00:32.158 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
2026-07-28 15:00:32.158 | self,
2026-07-28 15:00:32.158 | ^^^^^
2026-07-28 15:00:32.158 | ...<4 lines>...
2026-07-28 15:00:32.158 | conn,
2026-07-28 15:00:32.158 | ^^^^^
2026-07-28 15:00:32.158 | )
2026-07-28 15:00:32.158 | ^
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/orm/context.py", line 306, in orm_execute_statement
2026-07-28 15:00:32.158 | result = conn.execute(
2026-07-28 15:00:32.158 | statement, params or {}, execution_options=execution_options
2026-07-28 15:00:32.158 | )
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1421, in execute
2026-07-28 15:00:32.158 | return meth(
2026-07-28 15:00:32.158 | self,
2026-07-28 15:00:32.158 | distilled_parameters,
2026-07-28 15:00:32.158 | execution_options or NO_OPTIONS,
2026-07-28 15:00:32.158 | )
2026-07-28 15:00:32.158 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/sql/elements.py", line 526, in _execute_on_connection
2026-07-28 15:00:32.158 | return connection._execute_clauseelement(
2026-07-28 15:00:32.158 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
2026-07-28 15:00:32.159 | self, distilled_params, execution_options
2026-07-28 15:00:32.159 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.159 | )
2026-07-28 15:00:32.159 | ^
2026-07-28 15:00:32.159 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1643, in _execute_clauseelement
2026-07-28 15:00:32.159 | ret = self._execute_context(
2026-07-28 15:00:32.159 | dialect,
2026-07-28 15:00:32.159 | ...<8 lines>...
2026-07-28 15:00:32.159 | cache_hit=cache_hit,
2026-07-28 15:00:32.159 | )
2026-07-28 15:00:32.159 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
2026-07-28 15:00:32.159 | return self._exec_single_context(
2026-07-28 15:00:32.159 | ~~~~~~~~~~~~~~~~~~~~~~~~~^
2026-07-28 15:00:32.159 | dialect, context, statement, parameters
2026-07-28 15:00:32.159 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.159 | )
2026-07-28 15:00:32.159 | ^
2026-07-28 15:00:32.159 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
2026-07-28 15:00:32.159 | self._handle_dbapi_exception(
2026-07-28 15:00:32.159 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
2026-07-28 15:00:32.159 | e, str_statement, effective_parameters, cursor, context
2026-07-28 15:00:32.159 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.159 | )
2026-07-28 15:00:32.159 | ^
2026-07-28 15:00:32.159 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 2365, in _handle_dbapi_exception
2026-07-28 15:00:32.159 | raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
2026-07-28 15:00:32.159 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
2026-07-28 15:00:32.159 | self.dialect.do_execute(
2026-07-28 15:00:32.159 | ~~~~~~~~~~~~~~~~~~~~~~~^
2026-07-28 15:00:32.159 | cursor, str_statement, effective_parameters, context
2026-07-28 15:00:32.159 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.159 | )
2026-07-28 15:00:32.159 | ^
2026-07-28 15:00:32.159 | File "/app/.venv/lib/python3.14/site-packages/sqlalchemy/engine/default.py", line 952, in do_execute
2026-07-28 15:00:32.159 | cursor.execute(statement, parameters)
2026-07-28 15:00:32.159 | ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
2026-07-28 15:00:32.159 | sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: app_settings
2026-07-28 15:00:32.159 | [SQL: SELECT app_settings."key" AS app_settings_key, app_settings.value AS app_settings_value
2026-07-28 15:00:32.159 | FROM app_settings
2026-07-28 15:00:32.159 | WHERE app_settings."key" = ?
2026-07-28 15:00:32.159 | LIMIT ? OFFSET ?]
2026-07-28 15:00:32.159 | [parameters: ('configurations_migrated', 1, 0)]
2026-07-28 15:00:32.159 | (Background on this error at: https://sqlalche.me/e/20/e3q8)
2026-07-28 15:00:32.159 |
2026-07-28 15:00:32.162 | ERROR: Application startup failed. Exiting.
I even did an install against latest, confirmed that was working in general, and then changed to development, but still ended up there. This is not happening against my existing Container, which is functioning just fine:
So I cloned my data volume and pointed my net new install against that. With that, I was able to finally start:
2026-07-28 15:08:28.578 | /app/.venv/bin/python: can't open file '/app/migrate.py': [Errno 2] No such file or directory
2026-07-28 15:08:28.771 | 2026/07/28 15:08:28 Starting proxy server with MPEG-TS null packet support
2026-07-28 15:08:28.784 | 2026/07/28 15:08:28 Loaded routes from file:
2026-07-28 15:08:28.784 | "/proxy/1" -> "http://10.255.1.106:8090/hdmi1"
2026-07-28 15:08:29.849 | 2026/07/28 15:08:29 [PROXY] Proxy error: dial tcp 127.0.0.1:5593: connect: connection refused
2026-07-28 15:08:38.274 | INFO: Started server process [9]
2026-07-28 15:08:38.279 | 2026-07-28 15:08:38.278 - server - ADBTuner 20260728-1
2026-07-28 15:08:38.279 | 2026-07-28 15:08:38.278 - schema_verification - Verifying database schema (/app/.config/config.db).
2026-07-28 15:08:38.279 | INFO: Waiting for application startup.
2026-07-28 15:08:38.284 | 2026-07-28 15:08:38.284 - server - Database schema verification was successful.
2026-07-28 15:08:38.641 | 2026/07/28 15:08:38 Received updated routes, but no changes detected.
2026-07-28 15:08:38.676 | 2026-07-28 15:08:38.676 - tuner_management - Caching installed apps list for all tuners
2026-07-28 15:08:38.706 | 2026-07-28 15:08:38.706 - lib.controller_pool - Pool: creating controller for tuner 1 (10.255.1.168).
2026-07-28 15:08:38.708 | INFO: Application startup complete.
2026-07-28 15:08:38.783 | INFO: Uvicorn running on http://0.0.0.0:5593 (Press CTRL+C to quit)
And after getting going, I could confirm the import worked as expected, at least for all stations:

Thanks for this. I just pushed a new development build (20260728-2) in which you can actually start with a fresh database again. Oops.
@turtletank, would it be possible to add some type of IF conditioning inside Configurations? I have a fun one with the NFL Network in the ESPN app where only in that situation does it require an extra down press, so I'm thinking of something like:
"ABD_IF":
"condition": "||TARGET_URL_OR_IDENTIFIER||" == "4",
"commands": [
"input keyevent KEYCODE_DPAD_DOWN",
"sleep 1"
]
Thanks!
@turtletank, if you plan to make updates (add features) to how the config files get processed, would it also be possible to make ADB calls directly, or maybe a call to an external script where ADB commands could be implemented? Doing so would somewhat merge the capabilities of AH4C with the much cleaner ADBTuner app and GUI. Building a new image with ADB included is pretty easy... so this would add lots of capabilities to ADBTuner. Just a suggestion, but would appreciate your thoughts on that. Thanks.
I have a new setup using ADBtuner. The Foxone app initially plays but then pauses after 30 seconds on my Firestick 4K 2nd gen. If I use the remote I can resume playing. Not sure why it is doing that. Using LinkPi as encoder.
Any thoughts appreciated!
Thank you!
What specific Configuration are you using (one of the included configs, or is it custom)? What specific version of ADBTuner (latest or development)?
I'm using ADBtuner stable (20260723-1) - docker container/Unraid. Using included config and entered foxone://channel/fnc as a deeplink.
Update: I changed my setup to an ONN 4K plus. Same issue!
The reason I suggest the call for an external script is because I currently use the Spectrum app on a Firestick streaming to an encoder and have to create separate config files for each Spectrum channel. I was able to create a config that allows me to enter the channel number (mapped through ADB keypad), so it would lend itself nicely to being scripted with one of the ADBTuner channel fields. One of my config files is shown (just using Hallmark Channel as an example) ... so you can see how having an external script would make this very easy by adapting the 3 keyevent fields to the channel number. Also providing the script for others with a similar setup.
{
"name": "Spectrum - Hallmark Channel",
"author": "User",
"version": "1.0",
"description": "Spectrum - Hallmark Channel",
"uuid": "782a44d8-7e21-4f1e-9276-809c9454152e",
"global_options": {
"wait_for_video_playback_detection": false,
"use_fixed_delay": true,
"fixed_delay_seconds": 10,
"wait_after_post_playback_start_commands_seconds": 0
},
"pre_tune_commands": [
"input keyevent KEYCODE_HOME",
"sleep 3",
"am force-stop com.spectrum.stva.firetv",
"sleep 1",
"am start -n com.spectrum.stva.firetv/com.spectrum.stva.androidtv.ui.HostedMainActivity",
"sleep 12"
],
"tune_commands": [
"input keyevent KEYCODE_BACK",
"sleep 1",
"input keyevent KEYCODE_DPAD_RIGHT",
"sleep 1",
"input keyevent KEYCODE_DPAD_RIGHT",
"sleep 1",
"input keyevent KEYCODE_DPAD_CENTER",
"sleep 1",
"input keyevent 8",
"sleep 1",
"input keyevent 9",
"sleep 1",
"input keyevent 10",
"sleep 1",
"input keyevent KEYCODE_DPAD_CENTER",
"sleep 1",
"input keyevent KEYCODE_DPAD_CENTER",
"sleep 1"
],
"tune_match_text_commands": [],
"post_playback_start_commands": [],
"post_tune_commands": [
"input keyevent KEYCODE_HOME",
"sleep 1",
"am force-stop com.spectrum.stva.firetv",
"sleep 1",
"input keyevent KEYCODE_SLEEP"
],
"timed_keep_active_commands": [],
"source_file": "/app/.config/user_configurations/782a44d8-7e21-4f1e-9276-809c9454152e.json"
}
I actually have to second this.
I would really love to see a way to call external scripts through adbtuner. I think that's the standout feature of ah4c, it's the extensibility and customizability. I think the JSON configurations are very useful for keeping things simple. But once you start wanting to build something a little bit more complex, the ability to just write bash or Python could be really useful. I mean, obviously, this could be for advanced users, but this could really open up a whole plugin ecosystem for ADB Tuner.
I mean, I just built a sort of feature for ah4c using bash, effectively, wait for video playback detection just implement it in straight up bash. It works super well, and just like I kind of like that about ah4c that you can kind of just do anything, and it's like a playground, which is great for people like me who like to tinker. It's a sort of a feature because it's an external script, it's not actually part of the application.
I think this could really benefit ADBTuner just because you can have the slick interface and really easy to use configurations and repositories while also being able to tinker if you so choose.

