i8beef / node-red-contrib-castv2

MIT License
22 stars 14 forks source link

TTS Error: Load failed #64

Closed arachnida closed 11 months ago

arachnida commented 3 years ago

When trying to cast a text to my Google Home Hub Max device I get "Error: Load failed" back from the node. The very same flow does work when sending to my other Google Home Hubs, only the Max gives me this error. I tried other nodes that worked fine before a few days on my Max but they all give me an error so I guess a new FW update broke something on the Max.

Can you confirm this?

i8beef commented 3 years ago

I had another complaint about this earlier in the week. Google appears to have updated something that broke casting for some people. He had luck with factory resetting his Google Home device and setting it up again.

FedeBZ commented 3 years ago

Factory reset and Google Home device and setting it up again work for me. Google mini

Salan54 commented 3 years ago

Same here "Error: Load failed". With Micrologiciel Cast: 1.54.250118 (French Google Home). When I send TTS command with some text, I just hear a bip on the device, then the error message appears on NR. You talked of "factory reset" : where did you find it, please? On the "Home" application, I can "restart" the device (I did "restart" but to no avail...), but I did not find "factory reset"... Thanks for your advice.

FedeBZ commented 3 years ago

https://support.google.com/googlenest/answer/7073477

arachnida commented 3 years ago

I did a factory reset of my GH Hub max but that didn't help. To make it even worse the two other GH hubs (original, not Max) also stopped streaming the TTS mp3. They still worked fine yesterday. Pity I didn't check the FW version yesterday so I don't know if they have been updated. FW= 250320 Cast Version= 1.54.250320

arachnida commented 3 years ago

BTW, I can still cast an mp3 directly from my HD using media URL and type audio/mp3. Strange!

i8beef commented 3 years ago

To be clear, using translate.google.com in the manner that we are isn't technically supported. Google have experimented with adding limits to this free service a few times. Just opening up the following URL and reloading a bunch, I am eventually hitting a captcha challenge now...

https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&tl=en&ttsspeed=1&q=Test

I suspect they are experimenting again to lock down this usage... by chance was your experience preceded by a series of requests within a short time frame?

If this is the case, this will effect everyone using this hack eventually...

arachnida commented 3 years ago

Hi, No, I use this service about 3-5 times a day. Also it doesn’t explain why yesterday only my GH hub max was affected and not the other GH hubs. All are connected to the same RPI account. As I told before, also other nodes written by other programmers seem to be affected, so you are probably right that Google is restricting access or want money for the service. For some of the more static nodes that use TTS I will record an mp3 with the text, but I also have some nodes using the service to speak out the track title and artist playing on my Sonos or a temperature reading and I need to find a solution for those as well.

BTW, the link in your previous post gives a 404 error.

i8beef commented 3 years ago

I suspect that its just Google messing around with rate limits / deciding to throw captchas randomly to cut people out of using this. If I remember right, this is SUPPOSED to only be used to provide "examples" of their for profit TTS API... and everyone basically abuses it for this purpose. The actual Google TTS API requires account setup, which I've always wanted to avoid forcing people into despite "free" usage being limited to 1 million usages a month which should be way below all of our usage (Especially because they ask for a credit card that "we totally won't just charge at some point").

I might consider introducing a "provider" option for the TTS stuff to allow us to add new TTS providers as they come on line. That would allow at least a "full Google TTS API" option for those willing to go through the hassle of setting up an account, which I bet would sidestep this limit.

Google even has a 2.0 version of this endpoint that works completely differently (returns a base64 encoded binary instead of an MP3, I'm guessing specifically to kill our direct usage). Unfortunately to use that here, I'd have to proxy the call through an open endpoint just to wrap it in a base64 decode. That could be done (I think its how HomeAssistant ended up doing it), but I'm unsure I'd want to mess with that specifically in the cast node. I'd almost wanna create a secondary TTS node for the purpose, and even then, I'm willing to bet it has the same CAPTCHA limitations...

Going the route of "TTS MP3 serve must be handled outside the cast node" has some attractive qualities, like giving people the option of using any TTS service they happen to find, but definitely removes some of the use friendliness here. Technically, you could do this NOW with a simple media cast and a node-red HTTP endpoint setup appropriately.

When my laptop gets back from warranty repair, I'll look at adding a "provider" option to the TTS stuff and a limited implementation for the full Google TTS API to experiment.

That link works, its just, again, Google limits usage of the endpoint. Since you are clicking through, it has a REFERRER which Google shoots down (one of their earlier "limits" they added a few years back). Copy and paste the URL into the browser so there's no referrer and it should work just fine.

arachnida commented 3 years ago

Thanks for your explanation, I always like to know how things work! And the link is indeed working after a copy/paste! I also believe I once registered for a dev account at Google, but never used it. Have to take a look in my old mailbox. Stay safe! arachnida

i8beef commented 3 years ago

I'd like to keep this one open until we have good ideas on work arounds for those afflicted.

As I hinted, there IS a solve here for those that want to go setting up a Google Cloud account. The TTS type here is really just a shorthand for someone manually building a URL pointing at an MP3 and sending a regular MEDIA command with it. That is, it really just builds a URL automatically for you. So if you can source an MP3 from elsewhere, especially if it can be driven by passed params in the URL like the translate.google.com one is, then you can easily replace it with a direct media command.

The node-red-contrib-google-cloud node supplies a node that returns an MP3 Buffer using the credentials of a linked Google Cloud account. The sucky thing is you have to give Google your credit card to light this API up, and then navigate their super confusing console to figure out how to generate and download a "key file". I actually have this working after five minutes of tinkering with the following flow at /textToSpeech?text=My+test+string:

[{"id":"3bb2af23.8ba42","type":"http in","z":"8b1b11bf.ab7d8","name":"","url":"/textToSpeech","method":"get","upload":false,"swaggerDoc":"","x":880,"y":900,"wires":[["331e2a71.6fb956"]]},{"id":"68700862.931ed8","type":"http response","z":"8b1b11bf.ab7d8","name":"","statusCode":"","headers":{},"x":1160,"y":1020,"wires":[]},{"id":"331e2a71.6fb956","type":"function","z":"8b1b11bf.ab7d8","name":"","func":"let text = msg.payload.text;\n\nmsg.payload = text;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1000,"y":960,"wires":[["2f4fe9aa.b1c766"]]},{"id":"2f4fe9aa.b1c766","type":"google-cloud-text-to-speech","z":"8b1b11bf.ab7d8","account":"","keyFilename":"","name":"","languageCode":"en-US","gender":"FEMALE","encoding":"MP3","rate":1,"pitch":0,"voiceName":"","x":1180,"y":960,"wires":[["344f04e6.6dd11c"]]},{"id":"344f04e6.6dd11c","type":"function","z":"8b1b11bf.ab7d8","name":"","func":"msg.statusCode = 200;\nmsg.headers = {\n    \"content-type\": msg.payload.audio.mime\n};\n\nmsg.payload = msg.payload.audio.data;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1000,"y":1020,"wires":[["68700862.931ed8"]]}]

That allows me to just send through the MEDIA command like this:

return {
    payload: {
        type: "MEDIA",
        media: {
            url: "https://myurl.com/textToSpeech?text=My+test+string",
            contentType: "audio/mp3"
        }
    }
};

And my Google Home speeks it ok. Thats a lot of setup just to get the TTS functionality stable, but Google isn't giving us a lot of options if this becomes a thorn in the side of everyone here... Note the same thing could be done with ANY TTS service if you found another one you liked, you would just sub out the Google TTS node for whatever gives you an MP3, and it requires no functional change here. People that the translate.google.com hack still works for can continue to use that, and others can go down the rabbit hole of setting up services as needed until a better option presents itself.

cw-kid commented 3 years ago

I can also confirm Google Home TTS has stopped working for me as well. With the Error: Load failed and the speakers just make the ding sound. I only use node-red-contrib-castv2 for TTS from my Vera HA system. I also tried the other node-red-contrib-cast and that's doing the same thing with the ding sound and no TTS announcement heard. Hope you can get this working again even if I have to sign up for a Google API account or whatever I'd do it.

EDIT: I just tried that TEST url above in my browser and that is working and says "Test".

Thanks

i8beef commented 3 years ago

@cw-kid I'm unsure what this looks like for someone that doesn't already have a Google Developer account...

  1. Install node-red-contrib-google-cloud
  2. Copy the flow I have in my last message to create an endpoint in your node-red installation that will generate MP3s from Google TTS API
  3. Go to https://cloud.google.com/text-to-speech and walk through the steps to setup an account / billing (Note: if you plan to send more than 4 million characters through this in a month, might want to look at other options)
  4. Create a "credential" for your new Google Cloud account. When you do, it'll download a .JSON file. Copy and paste the contents into a new "credential" in the Text-to-speech node from the flow above.
  5. Change the message that gets sent to into the castv2 node to something like the following, changing out the URL for your node-red instance of course.
let text = "Whatever you want to say";
let encodedText = encodeURIComponent(text);
return {
    payload: {
        type: "MEDIA",
        media: {
            url: "https://urlOfYourNodeRedInstance/textToSpeech?text=" + encodedText,
            contentType: "audio/mp3"
        }
    }
};

This appears to work to me, and we shouldnt get hit with captchas this way... if Google's API setup wasn't so ugly I'd almost say I prefer this since it allows for ANY TTS service to be injected in there...

arachnida commented 3 years ago

It looks, at least over here in Belgium, that the GC platform is only free for 90 days? And indeed Google is asking for a CC because they want to know if I'm a robot or not, WTH?!

arachnida commented 3 years ago

I think I found a solution using the free (Max daily request=350 @ 100KB/request) "http://www.voicerss.org/" API. Only requires you to register to get a key, no CC. :-)

I injected the following in the castv2 node using a function node:

text = "Whatever you want to say";
return {
    payload: {
        type: "MEDIA",
            url: "https://api.voicerss.org/?key=InsertYourKeyHere=en-us&c=MP3&src=" + text,
            contentType: "audio/mp3"

    }
};

So far this works on all my GH Hubs.

elmigbot commented 3 years ago

FWIW, the Home Assistant implementation still works with no issue/error. It seems only node red's Cast and CastV2 is affected.

On a separate but related note, when it fails in node red, I also get an error in home assistant:

[homeassistant.components.cast.media_player] Failed to cast media https://translate.google.com/translate_tts?ie=UTF-8&q=hi%20there&tl=en&total=1&idx=0&textlen=8&client=tw-ob&prev=input&ttsspeed=1. Please make sure the URL is: Reachable from the cast device and either a publicly resolvable hostname or an IP address

i8beef commented 3 years ago

My understanding of the Google Cloud is that the 90 day free trial is really just a guarantee to not automatically charge you on usage. After that, they CAN charge you automatically without recourse (A fact I'm NOT pleased with)... but as long as you stay under their free tier limits you won't be.

Your alternative MP3 source is the most ideal for a lot of people. Google overcomplicates everything, so a simple api key protected endpoint like that is really ideal, and you can just sidestep the TTS usage in favor of building the URL yourself for a direct media cast with really any MP3 source.

Home Assistant's implementation uses the "new" version of this free endpoint, but they also take a significantly more complicated approach. The new endpoint doesn't return an MP3 file directly, it returns a base64 encoded file that requires unencoding before you can actually serve it to the chromecast. They do this by literally downloading, unencoding, and then HOSTING that file in a temporary cache they expose an endpoint for. To be clear, that's way more complicated than I'd like to put in this node's implementation...

However, one COULD do the same thing with my endpoint above, and just replace the Google Cloud node with a call to that new endpoint. Everything would basically be the same, you'd just unencode and load the response Buffer with the result of the call. If that new endpoint doesn't do the CAPTCHA thing (skeptical that if thats the case NOW Google wouldn't pull the same thing eventually with it), or use a NONCE to ensure usage from their pages only (If I were them, totally would do that), then this could be an easier alternative to the whole Google Cloud setup.

I might play with that too just to see if it works as a third option here.

arachnida commented 3 years ago

I must play a little more with the voicerss.org API as I find the result very muffled. Tomorrow I’ll have a look at the options in the API to get a clearer voice.

i8beef commented 3 years ago

Ok, so you could implement the textToSpeech endpoint against the newer Google TTS endpoint with this flow without using the Google Cloud account too:

[{"id":"41344e9a.6d9da","type":"function","z":"8b1b11bf.ab7d8","name":"Extract params","func":"let text = msg.payload.text;\nlet lang = 'en';\nlet slow = false;\nlet headers = {\n    \"Referer\": \"http://translate.google.com/\",\n    \"User-Agent\":\n        \"Mozilla/5.0 (Windows NT 10.0; WOW64) \" +\n        \"AppleWebKit/537.36 (KHTML, like Gecko) \" +\n        \"Chrome/47.0.2526.106 Safari/537.36\",\n    \"Content-Type\": \"application/x-www-form-urlencoded;charset=utf-8\"\n};\n\nreturn {\n    origMsg: msg,\n    headers: headers,\n    payload: 'f.req=' +\n        encodeURIComponent(\n            JSON.stringify([\n                [['jQ1olc', JSON.stringify([text, lang, slow ? true : null, 'null']), null, 'generic']],\n            ])\n        )\n};","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1040,"y":820,"wires":[["c5c8001e.6e4f6"]]},{"id":"c5c8001e.6e4f6","type":"http request","z":"8b1b11bf.ab7d8","name":"","method":"POST","ret":"txt","paytoqs":"ignore","url":"https://translate.google.com/_/TranslateWebserverUi/data/batchexecute","tls":"","persist":false,"proxy":"","authType":"","x":1210,"y":820,"wires":[["116b4c4e.de5eb4"]]},{"id":"116b4c4e.de5eb4","type":"function","z":"8b1b11bf.ab7d8","name":"Build response","func":"let origMsg = msg.origMsg;\n\norigMsg.statusCode = 200;\norigMsg.headers = {\n    \"Content-Type\": \"audio/mp3\"\n};\n\nlet result = eval(msg.payload.slice(5))[0][2];\nlet result2 = eval(result)[0];\n\norigMsg.payload = new Buffer(result2, 'base64');\n\nreturn newMsg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1040,"y":860,"wires":[["26541de0.f07992"]]},{"id":"26541de0.f07992","type":"http response","z":"8b1b11bf.ab7d8","name":"","statusCode":"","headers":{},"x":1190,"y":860,"wires":[]},{"id":"c0058fe7.b105a","type":"http in","z":"8b1b11bf.ab7d8","name":"","url":"/textToSpeech2","method":"get","upload":false,"swaggerDoc":"","x":810,"y":820,"wires":[["41344e9a.6d9da"]]}]

Just no guarantee that it doesn't end up suffering more breakages because of Google adding captchas or whatever.

i8beef commented 3 years ago

Note I created a WIKI page to track any alternatives we come up with here: https://github.com/i8beef/node-red-contrib-castv2/wiki/TTS-Alternatives

arachnida commented 3 years ago

Thanks for the Wiki Michael!

OT: I noticed some odd behaviour of your node during my testing. When injecting a msg containing the mp3 my RPI connection was lost for a few seconds and strange things started to happen. I use a Z-wave network to control my lights and some of my lights turned on without reason. I also noticed a Delay node that became unresponsive. I was able to find out what went wrong comparing good working flows and the baddie. The node that caused the trouble was still configured to speak text so the message and language options inside your node where still there while I injected a message to play media mp3. Removing the message and language options solved the bug. Do you prefer that I open another bug report for this as it is OT for this thread?

cw-kid commented 3 years ago

@i8beef

"Create a "credential" for your new Google Cloud account. When you do, it'll download a .JSON file. Copy and paste the contents into a new "credential" in the Text-to-speech node from the flow above."

I am stuck on this part. I have started the free trial and entered my credit card details.

In the Google Cloud Platform site, do I then go to "APIs and Services" on the menu on the left? I can then see at the top "Create Credentials" which looks like this:

image

Their web site is confusing !

Or do I have to enable this API first ?

image

i8beef commented 3 years ago

@arachnida open another ticket.

@cw-kid Yes, Google's interface isn't easy to navigate. I had to stumble through it too so I'll try and do this from memory (I'd like to get a better set of instructions around this piece in the WIKI).

  1. Yes you need to enable the Text-to-Speech API, which should just be a button click there if I remember right.
  2. From the "cloud console" I went to "APIs & Services" and then on the left there's a "Credentials" menu item. I then clicked "Create credentials" at the top and chose "Service account". In the screen that pops up, all I setup was the name of the account (I think I just used "tts"), and I didn't mess with anything else.

I am new to the Google Cloud stuff, though I already had a "project" created here for my Google Home implementation stuff which I am reusing here. I think I remember that requiring a Google Developer account setup, and project creation, etc., but it looks like maybe just clicking through the Google TTS API link generates all that for you?

If you feel up to it, I'd love to flesh out that documentation pieces on how to enable all this, as like you said, it isn't obvious.

cw-kid commented 3 years ago

OK many thanks I will give it a try and report back when I get stuck again LOL.

Cheers

cw-kid commented 3 years ago

I haven't proceeded yet any further with the Google Cloud setup.

However I have just noticed that TTS is working on my Google Home mini in my bedroom. However that mini is linked to a different Google account / email address and not my main regular Google account.

But the Google Home mini in my lounge TTS does not work on that one and that one is linked to my main regular Google account.

So this begs the question has my main email address / Google account been blocked by them but my secondary email address / Google account has not.

cw-kid commented 3 years ago

Back to the Google Cloud thing, I have enabled the TTS API and I have created a "Service Account" named TTS. However I cannot see where or how to download this JSON file ?

"Create a "credential" for your new Google Cloud account. When you do, it'll download a .JSON file"

Regarding a "Project" before the other day I created a new Project called "Google Home TTS" so everything I am doing is within that project etc.

Thanks

image

image

EDIT:

Think I have found it now, under the Service Account you then go to Keys and Add a new Key and now I see this screen:

image

There are also some instructions on this URL with older out of date screen shots:

https://flows.nodered.org/node/node-red-contrib-google-cloud

cw-kid commented 3 years ago

OK I have the credentials setup now and this test URL is working now in the web browser on my PC.

http://NODE-RED-IP:1880/textToSpeech?text=My+test+string

I am now stuck on the last part:

"Change the message that gets sent to into the castv2 node to something like the following, changing out the URL for your node-red instance of course."

text = "Whatever you want to say";
return {
    payload: {
        type: "MEDIA",
            url: "https://api.voicerss.org/?key=InsertYourKeyHere=en-us&c=MP3&src=" + text,
            contentType: "audio/mp3"

    }
};

Where do I connect the CastV2 node in the flow ?

image

In my old setup I could just send in HTTP Requests to node-red like the below URL and specify the TTS text and also specify the IP address of the Google Home speaker that I wanted to say the TTS.

192.168.1.101 would be the IP address of the Google Home speaker to send the TTS to.

http://NODE-RED-IP:1880/sceneTrigger?message=this%20is%20a%20test&ip=192.168.1.101

Another user on the Vera forum who is a bit of a node-red expert configured everything and got it all working previously here.

This is the Flow I was using before it broke last week:

image

Thanks

i8beef commented 3 years ago

I'm assuming that "function for TTS" is what was building the original TTS message. You just have to change it to instead do a MEDIA message that points to the URL endpoint you just created.

That is, the endpoint you just created and your cast flow are two different flows. The ENDPOINT is just a standalone replacement for translate.google.com. The CAST flow stays the same, you are just changing what URL you are pointing to (by changing it from a TTS message to a MEDIA message and pointing to the replacement standalone endpoint).

Aside: Your two account thing wouldn't be it, translate.google.com isn't using your tied Google account at all in the TTS handling. Its just luck of the draw if the SESSION that the cast target has got flagged by google for a CAPTCHA, and then once its flagged you don't have a way of (a) satisfying the CAPTCHA to continue, or (b) clearing the session. This is why factory resetting the unit worked for some people I think, it basically hard clears EVERYTHING including that session, and then you're good again... until that Home gets flagged again on a subsequent request.

cw-kid commented 3 years ago

I've got it working now again, thanks to some help from the other Vera user who set it up originally. I will try and post what we have done on the Vera forum.

hystrix1 commented 3 years ago

Many thanks for the tips on this page to get TTS working again :-)

To get the VoiceRSS example given here working, this is the Node-RED function I used:

let text = "Motion has been detected";
return {
    payload: {
        app: "DefaultMediaReceiver",
        type: "MEDIA",
        media: {
            url: "https://api.voicerss.org/?key=your_api_key_here&hl=en-gb&c=MP3&src=" + encodeURIComponent(text),
            contentType: "audio/mp3"
        }
    }
};

The example given seems to have a few things missing, but the code above I have working ok.

i8beef commented 3 years ago

The example given seems to have a few things missing

The example on the TTS page? I just threw that together quickly, so if there's an improvement to be made, I'm happy to take suggestions 😄

i8beef commented 3 years ago

A note here: I am without my primary laptop at the moment, but when it gets back I'm going to explore another option for Google Home / Assistant SPECIFIC implementations (Won't solve pure cast issues). I think I can create a new node that makes node-red act as a full google assistant agent, i.e., you could send any text string into it like "broadcast to kitchen This is a test" (or any Google Assistant supported command string I think if I understand this right). The only downside is that node will require a Google Cloud account for sure to light up the Google Assistant API for your account, but maybe you could avoid attaching a credit card that way which I'm leery of.

No promises, but I see how that could be useful for all kinds of reasons. Im actually curious how many here are actually using the TTS part for a CHROMECAST instead of a Google Home device... I suspect its mostly the latter case anyway.

i8beef commented 3 years ago

Well I played a bit with the Google Assistant API, and I'm not sure that is actually a good way to go... I might still pursue that for other usages, but in order to use that API you not only need to have a full Cloud account (so already as bad as setting one up for TTS API usage), but you ALSO need to setup a full OAuth credentials flow to use it including token management (so much WORSE than the TTS API setup unless you wanted to use it for other Google Home things).

So as an alternative, it would just be even worse for most of you.

balthus commented 3 years ago

Hello, I also hit this bug since few days and bump into this thread. I have 3 nests and one stopped working with the cast V1. I tried on it the new V2, but it also just make a sound, but no speech. I'm from Belgium (maybe region is also influencing this?). It was really easy to send some notifications before, so I hope there will be a solution.

frogale commented 3 years ago

Hi to all.. I have working endpoint (in my browser it is playing fine) but with castv2 i got only "Error: Load failed". When I put some MP3 file to media url of castv2 it is streamed to GH ok.. But no luck with my endpoint.. Any suggestions?

image

i8beef commented 3 years ago

The cast target doesn't like your MP3 for some reason. Are you sure your endpoint is setting the Content-Type header? Are you running with authentication on said endpoint (Open the URL in another browser that isn't logged in, or in incognito mode if on Chrome)?

frogale commented 3 years ago

When I download output MP3 from my endpoint place it on my webserver and serve it with castv2 it is also working.. I am using endpoint authentication as https://user:password@ipaddress:port/textToSpeech?text=blabla and browser can play this url directly.. Using your flow example on this page.. But in private mode Chrome is asking for username again.. this is normal behavior, right?

i8beef commented 3 years ago

My best guess for you is one of two things.

  1. Is your HTTPS cert a real cert or self signed? I seem to remember something about it only accepting real certs...
  2. I've seen a couple of MENTIONS (but no explicit documentation) of cast devices NOT supporting basic authentication via URL.

If 1 checks out, you might need to either proxy that (nginx example you might try: https://serverfault.com/questions/230749/how-to-use-nginx-to-proxy-to-a-host-requiring-authentication) or serve it some other way that doesn't require basic auth...

jbrown123 commented 3 years ago

FYI, just tested the google TTS url. Indeed, if there is a referrer (if you just click the link) it doesn't work. If you just copy and paste into the browser URL, it works fine.

https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&tl=en&ttsspeed=1&q=Test+text+to+speech

However, when I put this in as a source URL for a media playback, it also fails (playing back other MP3 audio works fine so I know the flow is correct).

I then tried to just DL the audio using both wget and curl. Both return a 403 error even though the exact same URL (copied and pasted) works just fine in a browser. That makes me think it is a header problem.

Sure enough, passing a user-agent that looks like a browser allowed me to download the mp3 as expected.

"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36"

wget -U "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36" --no-check-certificate -O test.mp3 "https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&tl=en&ttsspeed=1&q=Test+text+to+speech"

@i8beef - you might try adding a user-agent header to your cast request and see if that fixes it. I currently have this problem (again) so I'd be happy to test for you.

i8beef commented 3 years ago

While that could be possible, we aren't in control of the headers that the Google cast device sends in its request.

jbrown123 commented 3 years ago

But you can create a pass through, using node-red that will set the headers correctly. I created a flow that listens on /TTS and redirects that through the Google translate API after setting headers correctly. It works fine (for the moment, until Google changes things again). Essentially I split out the TTS to MP3 generation into a separate process. I changed my existing flows to make a MEDIA request to my new /TTS flow rather than using castv2's TTS request to the google translate API.

The good news is that the local Google Home devices on my network are fine being passed a local http:// URL using a NAT IP address on my internal network.

Another advantage of this new flow is that I now have only a single flow that I need to change if I want to switch TTS providers. Previously I had to go edit every node that I had setting up the JSON TTS requests for Google. These were scattered across a bunch of flows.

I also implemented a file cache of MP3s for common messages that I request (e.g. "garage door opening", etc.). My new TTS flow now checks to see if I have a local MP3 for the text being requested. If so, it simply returns that file rather than sending the request off to Google or another TTS provider.

My new input to castv2 looks like this (it expects the text to be spoken in the msg.payload):

{
   "app": "DefaultMediaReceiver",
   "type": "MEDIA",
   "media": {
        "url": "http://10.0.1.10:1880/TTS/" & payload,
        "contentType": "audio/mp3"
   }
}
i8beef commented 3 years ago

Hmmm that is true, in that regard you COULD put it behind your own endpoint like the others... though at that point I might recommend using the "new" API instead of the old one anyway, since I suspect at some point Google will kill this one altogether (i.e., I think this CAPTCHA is the "hint" to go find another option without just outright deleting it... never can tell with Google).

I think I'll probably end up pulling the TTS "direct" support at some point in the future over all this, and instead fallback to a set of recommended alternatives like this. I will look at adding this as an option to the "TTS Alternatives" WIKI doc this weekend.

The MP3 caching is a great idea, but also probably more setup than most people here will want to manage (though I might steal it 😄)

Just a note, the CAPTCHA seems to pop randomly on people, so though you have a working implementation right now, I'll be interested to see if you hit the same issue eventually here. People are seeing some of their homes working, and some not, and resetting them get them back to working for a time (i.e., Google is flagging individual sessions, not even IPs here or something)... so I'm a little skeptical that you won't end up still getting hit with the CAPTCHA eventually.

jbrown123 commented 3 years ago

I agree, they will likely slowly kill the old one by turning on captcha which will basically force everyone to the new version.

My issue was definitely one of the user-agent header. Setting it allowed it to work, not setting it failed every time.

Here's the flow I have that implements my /TTS service with the caching. Note that I'm not bothering to cache strings automatically. I know which ones of mine are fixed and which are variable (have time or other variables) so I manually cached the ones I knew didn't change. However, that also gives you the option to put any audio you want in place of a particular message. So if you send "RickRoll Me" as the text, you could play a particular MP3 in response. Or you could use it to send alarm sounds, barking dogs, or other things. Feel free to steal away ...

[{"id":"42ee0552.fd741c","type":"tab","label":"TTS","disabled":false,"info":""},{"id":"79b4d80d.fe8e68","type":"http response","z":"42ee0552.fd741c","name":"","statusCode":"","headers":{},"x":870,"y":300,"wires":[]},{"id":"1022b057.e0bec","type":"change","z":"42ee0552.fd741c","name":"Set headers","rules":[{"t":"set","p":"headers.content-type","pt":"msg","to":"audio/mp3","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":670,"y":200,"wires":[["79b4d80d.fe8e68"]]},{"id":"3f7d7eae.96c6f2","type":"http request","z":"42ee0552.fd741c","name":"Google TTS","method":"GET","ret":"bin","paytoqs":"ignore","url":"https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&tl=en&ttsspeed=1&q={{{payload}}}","tls":"","persist":false,"proxy":"","authType":"","x":670,"y":380,"wires":[["79b4d80d.fe8e68"]]},{"id":"4b7c32c8.fc5aec","type":"file in","z":"42ee0552.fd741c","name":"Read TTS MP3","filename":"","format":"","chunk":false,"sendError":false,"encoding":"none","x":480,"y":200,"wires":[["1022b057.e0bec"]]},{"id":"6b7ff6d2.504348","type":"change","z":"42ee0552.fd741c","name":"Set string as payload","rules":[{"t":"set","p":"payload","pt":"msg","to":"req.params.string","tot":"msg"},{"t":"set","p":"headers","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"headers.user-agent","pt":"msg","to":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":440,"y":380,"wires":[["3f7d7eae.96c6f2"]]},{"id":"6e58569c.84d398","type":"change","z":"42ee0552.fd741c","name":"set filename","rules":[{"t":"set","p":"filename","pt":"msg","to":"$flowContext('ttsPath') & req.params.string & \".mp3\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":210,"y":280,"wires":[["e2b29a32.acf008"]]},{"id":"6137acc8.bee464","type":"http in","z":"42ee0552.fd741c","name":"TTS","url":"/TTS/:string","method":"get","upload":false,"swaggerDoc":"","x":50,"y":280,"wires":[["6e58569c.84d398"]]},{"id":"efc66375.6f54a","type":"config","z":"42ee0552.fd741c","name":"TTS config","properties":[{"p":"ttsPath","pt":"flow","to":"/YOUR-PATH-HERE/","tot":"str"}],"active":true,"x":170,"y":200,"wires":[]},{"id":"e2b29a32.acf008","type":"fs-ops-access","z":"42ee0552.fd741c","name":"test access","path":"","pathType":"str","filename":"filename","filenameType":"msg","read":true,"write":false,"throwerror":false,"x":410,"y":280,"wires":[["4b7c32c8.fc5aec"],["6b7ff6d2.504348","de130b81.9ddcb8"]]},{"id":"bb6542ea.cb5a9","type":"comment","z":"42ee0552.fd741c","name":"Read Me","info":"This flow will either read a file off disk or pass the request through a TTS service on the web.\n\nEither way, it returns an MP3 which can then be played (cast) via Google Home devices.\n\nIn the TTS Config node, ttsPath should be set to the fully qualified path of the directory where TTS files are stored. For example \"/home/nodered/TTS/\". Be sure it ends with a slash.\n\nThe files must be named the exact same as the message text, including case, with an extension of \".mp3\". For example, \"Welcome home.mp3\" for a message request of \"Welcome home\".\n\nSome alternative online TTS services are listed here:\nhttps://github.com/i8beef/node-red-contrib-castv2/wiki/TTS-Alternatives\n\nA properly configured example TTS for cast change node is included. This expects the text to be spoken in the msg.payload. It will output the appropriate object to pass to a castv2 node.","x":100,"y":80,"wires":[]},{"id":"de130b81.9ddcb8","type":"debug","z":"42ee0552.fd741c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"error.message","targetType":"msg","statusVal":"","statusType":"auto","x":650,"y":287,"wires":[]},{"id":"95c239ce.b87bb8","type":"change","z":"42ee0552.fd741c","name":"TTS for cast","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\t   \"app\": \"DefaultMediaReceiver\",\t   \"type\": \"MEDIA\",\t   \"media\": {\t        \"url\": \"http://YOUR-SERVER-NAME-OR-IP/TTS/\" & payload,\t        \"contentType\": \"audio/mp3\"\t   }\t}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":290,"y":100,"wires":[[]]}]
frogale commented 3 years ago

My best guess for you is one of two things.

  1. Is your HTTPS cert a real cert or self signed? I seem to remember something about it only accepting real certs...
  2. I've seen a couple of MENTIONS (but no explicit documentation) of cast devices NOT supporting basic authentication via URL.

If 1 checks out, you might need to either proxy that (nginx example you might try: https://serverfault.com/questions/230749/how-to-use-nginx-to-proxy-to-a-host-requiring-authentication) or serve it some other way that doesn't require basic auth...

Cert is signed.. but authentication is problem I think.. And I have to use authentication.. I had to make simple PHP script (without authentication, only source IP filetered) outside of my nodered and I am calling it with regular cast node.. Script is using Google Text-to-speech API with my own key and return MP3 file for casting.. and it is working..

jvanderzande commented 3 years ago

I've taken the route to shell curl which downloads the mp3 from translate.google.com, saving it in a location on the RPI that is accessible via NGINX. This URL is then used by node-red-contrib-castv2 and set type "MEDIA" to cast the text. Working fine for me and avoid the hassle to setup the Google stuff. Thought I share it here so other might like that approach too. Still haven't figured out why I can download it fine with Curl without any limitations like it is also working in the browser when using the URL there.

i8beef commented 3 years ago

Thank you @frogale for confirming that the cast target can't deal with basic auth, as I couldn't find that documented anywhere.

jbrown123 commented 3 years ago

@jvanderzande I found it was an issue with the user-agent header in my case. I have NR doing the call to translate.google.com with a copy of my browser's UA & it works fine.

douvers commented 3 years ago

I've been having this problem as well with just a single tone playing on my google home devices instead of the voice, but today it has started working again.

The voice is slightly different with the same Australian accent but it's not as 'polished' as the old one.

I haven't done anything to restore it and I'm just reporting it to you that its working again for me.

zeroalphagit commented 3 years ago

I had the same problem with casting TTS (text to speech) which started 3 days ago. But today it started working again.

i8beef commented 3 years ago

I will leave the TTS option in for now, but will also maintain the WIKI page for options when it eventually goes bad again. I may also change the readme to point at this for better visibility.

I think I'll continue to use my alternative setup for now, though. This is like the third time they've broken this in as many years. Once I have cleaned up the documentation a bit and added those pieces, I'll leave this open for a while in case any other developments or ideas come up.