postlund / pyatv

A client library for Apple TV and AirPlay devices
https://pyatv.dev
MIT License
836 stars 89 forks source link

atvremote machine readable output #475

Closed jdebardi closed 4 years ago

jdebardi commented 4 years ago

What feature would you like? Option for atvremote scam to output something nice like JSON

Describe the solution you'd like Would make it much easier to read info about a device compared to parsing the machine readable output it has now.

Any other information to share? I realise atvremote is supposed to be a testing tool, but lots of people don’t want to implement a full pyatv program themselves. But with JSON output of atvremote scripters will be able to use it 100 times more easily/reliably.

postlund commented 4 years ago

This has already been requested in #153 and as you say, atvremote is more of a reference application for testing and not for scripting. I don't strictly oppose adding support for other kinds of outputs, but it's hard to do in a good way without atvremote exploding in size and complexity. It might be better to create another application for scripting purposes with a reduced feature set (you probaly don't need things like pairing, CLI, extensive help texts, etc. here). Support for scanning and all interface methods might be sufficient. What do you think?

jdebardi commented 4 years ago

Sorry, I’ll admit I didn’t read back over the previous requests. Indeed a stripped back version that just scans and can send commands would be fine as well. I proposed it in response to seeing #220 as that is going to be a breaking change for me (and others that use atvremote in a similar way I guess).

postlund commented 4 years ago

No worries 😄

Yeah, it will likely will (depending on how you have implemented the matching). But we'll aim for a reduced version then, it will be much better and easier to implement. We can call it atvscript or something. Maybe we can add a small REST API or something in the future it can run in the background, to avoid having to re-connect all the time between commands.

jdebardi commented 4 years ago

That would be lovely

At the moment I’m scanning regularly and caching the port for MRP (I think you are/planning to do similar for some other pyatv uses as well?), so a background running atvscript could conceivably also take that over as well so a script just asks to send a command to an ATV Id and not have to pass the current port etc. Nice to have feature but would make sense if it’s going to be sitting there running anyway!

Appreciate you considering this. Happy to help any way I can with testing etc when you get to it

postlund commented 4 years ago

Yeah, I see a need for it. The alternative would be to at least include an example for it so that it's easy to create a small application for this purpose.

Another "solution" is to cache data, like port and IP address, when implementing #243. This way a lot of data is stored on disk already, so there's no need to do all zeroconf stuff which would speed up a lot (but not remove all the connection set up). It's completely best-effort since this data will become old and not work at some time, so mitigations would be needed in that case.

postlund commented 4 years ago

@jdebardi I will start with a simple script that supports a few basic commands, nothing running in background. Do you have any requests regarding functionality?

jdebardi commented 4 years ago

Main thing is scan output has ip/port/Id So need an array (devices) of hashes (info)

postlund commented 4 years ago

Are you still using manual settings (--manual)? Because if you are you should migrate to unicast scanning instead. In that case you only need address and an identifier.

jdebardi commented 4 years ago

I am, and for a reason, even unicast is WAY too slow. I want to send command instantly to the cached port I know about, not wait for it to learn the port.

crxporter commented 4 years ago

I just saw this. I would also love to have a simplified version of atvremote! I'm running atvremote and piping the output to node red (javascript). Then I have a function to "read" the output and convert it back to numbers that control my home automation (turn off the lights when something plays, turn the bathroom light on for pause breaks, etc)

A simplified version would be very cool. This is what my function looks like, it's very basic: Screen Shot 2020-03-17 at 10 00 54 AM

I also saw #153, that's probably more along the lines of what I'd like. I'm not great in python- I don't think I have the skills to make this happen. But if you're making a simplified atvremote (with JSON output!) - it would be awesome to include push updates of playing information!

postlund commented 4 years ago

@jdebardi I get a deja vu feeling here, we have covered this before, right? It is strange that it would take way more time. Just tried both variants myself and they are equally fast more or less (within margin of errors). Can you try the same thing as I did below? Preferably with master since I've made significant speed ups compared to 0.4.0.


$ time atvremote -s 10.0.10.81 playing
  Media type: Music
Device state: Paused
       Title: Ordinary World (Live)
      Artist: Duran Duran
       Album: From Mediterranea With Love - EP
       Genre: Rock
    Position: 1/395s (0.3%)
      Repeat: Off
     Shuffle: Off

real 0m0.538s
user 0m0.476s
sys  0m0.020s

$ time atvremote --address 10.0.10.81 --port 49152 --protocol mrp --manual --id test playing
  Media type: Music
Device state: Paused
       Title: Ordinary World (Live)
      Artist: Duran Duran
       Album: From Mediterranea With Love - EP
       Genre: Rock
    Position: 1/395s (0.3%)
      Repeat: Off
     Shuffle: Off

real 0m0.540s
user 0m0.460s
sys  0m0.036s
postlund commented 4 years ago

@crxporter Yeah, that is what I'm aiming for. This is what I have at the moment:

$ atvscript  scan
{"result": "success", "devices": [{"name": "Apple\u00a0TV", "address": "10.0.10.123", "identifier": "xxx"}, {"name": "Vardagsrum", "address": "10.0.10.81", "identifier": "xxx"}]}

atvscript  -s 10.0.10.81 playing
{"result": "success", "hash": "azyFEzFpSNOSGq9ZvcaX4A\u2206DcpumkUoRty+R098MQeIKA", "media_type": "music", "device_state": "paused", "title": "Ordinary World (Live)", "artist": "Duran Duran", "album": "From Mediterranea With Love - EP", "genre": "Rock", "total_time": 395, "position": 1, "shuffle": "off", "repeat": "off"}

atvscript  -s 10.0.10.81 menu
{"result": "success", "command": "menu"}

Some rudimentary error handling:

$ atvscript  -s 10.0.10.82 menu
{"result": "fail", "error": "device_not_found"}

Any exception will be caught as well with message stored in the result as "exception". The goal is to always return JSON output (or whatever format is used). I can add push updates to this as well, no problems!

crxporter commented 4 years ago

@postlund for me that atvscript ... playing looks awesome. Move that to push updates and I’ll be able to clean up my node red considerably.

Thanks as always, great work!

postlund commented 4 years ago

@crxporter Yep, will be more or less the same:

$ atvscript -s 10.0.10.81 push_updates
{"result": "success", "hash": "azyFEzFpSNOSGq9ZvcaX4A\u2206DcpumkUoRty+R098MQeIKA", "media_type": "music", "device_state": "paused", "title": "Ordinary World (Live)", "artist": "Duran Duran", "album": "From Mediterranea With Love - EP", "genre": "Rock", "total_time": 395, "position": 1, "shuffle": "off", "repeat": "off"}

Updates will be printed as they happen. I might be able to push something later tonight for testing purposes.

postlund commented 4 years ago

So, initial work is up in #506. With that PR, the atvscript is available that supports some basic commands. Some kind of documentation is here (it will be available at pyatv.dev once merged). Please try it out and let me know what you think.

postlund commented 4 years ago

I merged #506 now, so it's possible to experiment with atvscript by installing from master. Still no "daemon" mode, that will have to be added over time. A decision must be made regarding how to interact with that script (is reading from stdin ok? REST API?), deal with issues when not connected with the device, how to run in background, etc. Feel free to come with input and PRs.

bytedealer commented 4 years ago

Hi @postlund,

great progress!

I downloaded the master and tried "atvscript scan". Unfortunately I got an error message:

Traceback (most recent call last): File "/usr/local/bin/atvscript", line 11, in <module> load_entry_point('pyatv==0.5.0', 'console_scripts', 'atvscript')() File "/home/pi/.local/lib/python3.5/site-packages/pkg_resources/__init__.py", line 490, in load_entry_point return get_distribution(dist).load_entry_point(group, name) File "/home/pi/.local/lib/python3.5/site-packages/pkg_resources/__init__.py", line 2859, in load_entry_point return ep.load() File "/home/pi/.local/lib/python3.5/site-packages/pkg_resources/__init__.py", line 2450, in load return self.resolve() File "/home/pi/.local/lib/python3.5/site-packages/pkg_resources/__init__.py", line 2456, in resolve module = __import__(self.module_name, fromlist=['__name__'], level=0) File "/usr/local/lib/python3.5/dist-packages/pyatv/__init__.py", line 261 scanner: BaseScanner ^ SyntaxError: invalid syntax

Is anything wrong with my setup? Or is it a error inside pyatv?

Have a great day! HFM

postlund commented 4 years ago

Hi! It is because of python 3.5, you need to upgrade to 3.6 or later. Due to various improvements I had (and also wanted) to make a bump.

bytedealer commented 4 years ago

Hi @postlund,

great hint!

Thanks a lot!

bytedealer commented 4 years ago

I merged #506 now, so it's possible to experiment with atvscript by installing from master. Still no "daemon" mode, that will have to be added over time. A decision must be made regarding how to interact with that script (is reading from stdin ok? REST API?), deal with issues when not connected with the device, how to run in background, etc. Feel free to come with input and PRs.

Hi @postlund,

I think a REST API would be much more easier for a lot of people.

Thanks for your good work! HFM

postlund commented 4 years ago

@bytedealer That is probably what I will aim for. Any functionality that I can add now (non-REST related) that would help you? Would prefer if we could find a definition of done for this issue and open new issues with more clear goals, as it helps me a lot. It's just navigating in the dark otherwise. And by that I also mean that you can open additional issues with feature requests.

crxporter commented 4 years ago

To me this is two separate projects:

1- the functioning atvscript which can be used by those who want JSON data coming back (play state, pairing, push, etc) 2- a REST API which could be left running as a daemon to send commands (send button presses after pairing)

Seems like (1) is pretty well done and (2) is another issue?

crxporter commented 4 years ago

I just installed the update with atvscript and it is awesome for my usage.

I put it as a node red daemon node running atvscript ... push_updates. This puts the json right into my node red environment, one instance for each Apple TV in the house. It’s much simpler than searching the atvremote text output.

Fantastic work thank you!

postlund commented 4 years ago

@crxporter I was thinking more along the line of adding support for a small REST-server directly into atvscript. I might prototype it...

Great that it seems to fill your needs 😊 Just write a feature request if you need any new features!

bytedealer commented 4 years ago

@bytedealer That is probably what I will aim for. Any functionality that I can add now (non-REST related) that would help you? Would prefer if we could find a definition of done for this issue and open new issues with more clear goals, as it helps me a lot. It's just navigating in the dark otherwise. And by that I also mean that you can open additional issues with feature requests.

Hi @postlund,

thanks for your offer!

What about PowerState.On and PowerState.Off?

Bye HFM

crxporter commented 4 years ago

@postlund I'm working through things with atvscript - is there any way to add a field with the name of the app playing or some kind of app identifier?

For example I'd like to ignore everything happening with PBS Kids, it doesn't appear to behave the same as other apps (going to menus changes to "paused" instead of "idle").

postlund commented 4 years ago

@crxporter Yes, I can add that.

postlund commented 4 years ago

@crxporter Power state changes are not logged with push_updates:

$ atvscript -s 10.0.10.81 push_updates
{"result": "success", "power_state": "off"}
{"result": "success", "hash": "azyFEzFpSNOSGq9ZvcaX4A\u2206DcpumkUoRty+R098MQeIKA", "media_type": "music", "device_state": "paused", "title": "Ordinary World (Live)", "artist": "Duran Duran", "album": "From Mediterranea With Love - EP", "genre": "Rock", "total_time": 395, "position": 1, "shuffle": "off", "repeat": "off", "app": "Musik", "app_id": "com.apple.TVMusic"}
{"result": "success", "power_state": "on"}
{"result": "success", "power_state": "off"}

{"result": "success", "push_updates": "finished"}

@crxporter App display name and identifier are included in "playing" output (same for push_updates):

{
  "result": "success",
  "hash": "azyFEzFpSNOSGq9ZvcaX4A∆DcpumkUoRty+R098MQeIKA",
  "media_type": "music",
  "device_state": "paused",
  "title": "Ordinary World (Live)",
  "artist": "Duran Duran",
  "album": "From Mediterranea With Love - EP",
  "genre": "Rock",
  "total_time": 395,
  "position": 1,
  "shuffle": "off",
  "repeat": "off",
  "app": "Musik",
  "app_id": "com.apple.TVMusic"
}

Documentation at https://pyatv.dev/documentation/atvscript/ is updated as well. Enjoy!

crxporter commented 4 years ago

I should be able to work with that! Thanks!

postlund commented 4 years ago

Maybe it's ok if I close this and you write additional feature requests, no?