FooSoft / anki-connect

Anki plugin to expose a remote API for creating flash cards.
https://foosoft.net/projects/anki-connect/
Other
1.94k stars 220 forks source link

how do we attach a local audio file to back content and make it play automatically on back content showing ? #95

Open redstoneleo opened 6 years ago

redstoneleo commented 6 years ago

how do we attach a local audio file to back content and make it play automatically on back content showing ?

test code if you need for test

import requests

r = requests.post('http://127.0.0.1:8765', json={
    "action": "addNote",
    "version": 6,
    "params": {
        "note": {
            "deckName": "英酷词典",
            "modelName": "Basic",
            "fields": {
                "Front": "front content",
                "Back": "back content"
            },
            "tags": [
                "yomichan"
            ],
            "audio": {#won' work right now
                "url": 'file:///D:/BaiduYunDownload/%E7%BC%96%E7%A8%8B/Python/Project/EngkuDict/positioned.mp3',  
                "filename": r"D:\Python\Project\EngkuDict\positioned.mp3",
                "fields": [
                    "Back"
                ]
            }
        }
    }
})

print(r.json())
derxe commented 6 years ago

I first thought that you cannot use your local files for your url but I was wrong. AnkiConnect uses urllib2 to download the audio files and it seems to support local files. No need to encode URL (this part: %E7%BC%96%E7%A8%8B), you can use unicode. Unless you are sending this to a server that expects URL to be encoded. You can check if your URL is correct with the following code

>>> import urllib2
>>> url="<Your URL>"
>>> urllib2.urlopen(url).read()
##you should see some output (probably some random looking data in case of an mp3 file##

The filename parameter should not be full path to the file. This is just the filename used to save your file into anki media collection folder. So you should change that.

Also if you already have files on your own computer why not just copy them directly to the anki media folder and just add [audio:] to the desired filed.

But I think I found the problem! The problem is with AnkiConnect. more precisely with download function (line 349):

 def download(self, url):
        resp = web.urlopen(url, timeout=URL_TIMEOUT)
        if resp.code == 200:
            return resp.read()
        else:
            raise Exception('return code for download of {} was {}'.format(url, resp.code))

It is checking for response 200, but if the file is local the response is set to None. I tried manually deleting if and it worked without a problem.

I think it would be better to check for any exceptions thrown by urlopen() function and than raise our own exception.

I also noticed that when adding files with the same filename instead of throwing error a new file is created with number attached at the end like " (1).mp3" This is only true if the new file is different from the currently existing one.

redstoneleo commented 6 years ago

Thanks very much !

  1. The filename paramater should be full path to the file.

    No, as I have tested , you can just provide a filename(not including the path), it is used as the filename saving into the anki media collection folder as you said.

  2. The anki media folder location might differ in different computers , how can I always get the correct location in this case ?

  3. It is checking for response 200, but if the file is local the response is set to None. I tried manually deleting if and it worked without a problem.

    I think you are right, we should change the code to support local files , then I won't need to bother with the above anki media folder location problem :)

  4. I also noticed that when adding files with the same filename instead of throwing error a new file is created with number attached at the end like " (1).mp3" This is only true if the new file is different from the currently existing one.

    I think it is involved with skipHash field https://github.com/FooSoft/anki-connect#notes

redstoneleo commented 6 years ago

BTW, it seems tags is a required filed , what is it used for ?

derxe commented 6 years ago

1. I made a typo 😅. It is fixed now.

4. SkipHash parameter does not solve the problem. It is solving the problem that when you provide a wrong URL to the server instead of returning error code a stub mp3 file is returned. Try it >here<. If you download the audio file above and create md5 hash you will get the same hash as in the example: 7e2c2f954ef6051373ba916f000168dc.

This skipHash parameter is useful really only in the case described above. You could try preventing downloading an error stub page, but the problem is that if the return data has dynamic values and is ever so slightly changed the skipHash won't work.

So sadly the skipHash function does not solve the problem with duplicated filenames.

I also couldn't understand how skipHash works when reading the api docs for the first time. I only got it now when looking at the code and trying to compute md5 hashes for myself. I think it would be nice to update the api docs to make the usage of skupHash clearer.

derxe commented 6 years ago

BTW, it seems tags is a required filed , what is it used for ?

They are used for tagging the notes :) From anki manual:

Below the fields is another area labelled “Tags”. Tags are labels that you can attach to your notes, to make organizing and finding notes easier. You can leave the tags blank if you wish, or add one or more of them.

Not including them should not be a problem. This is another thing that could get fixed.

YOnoda commented 5 years ago

Has anyone solved this issue? I tried to add my local audio file but it didn't work. For example in the following audio parameter:

return {
                "url": "/Users/me/Downloads/anki_audio_fr/" + filename
                "filename": filename,
                "fields": [
                    "Front", "Back"
                ]
    }

This created the following card:

front/Users/me/Downloads/anki_audio_fr/pronunciation_fr_précurseur.mp3 download failed with error Invalid URL '/Users/me/Downloads/anki_audio_fr/pronunciation_fr_précurseur.mp3': No schema supplied. Perhaps you meant http:///Users/me/Downloads/anki_audio_fr/pronunciation_fr_précurseur.mp3?

I also tried the suffix file:// but it also didn't work. How should I set the url and filename field? The audio file exists in the path.

FooSoft commented 5 years ago

If you want to add audio, you should be using the media APIs. They take base64-encoded data instead of file URLs.

YOnoda commented 5 years ago

@FooSoft But after that how can I add the media file to the note? I think what you are saying is adding media files to the Anki database while what I'm saying is adding media files to the notes. The note API (addNote) seems to only consider the media file as a remote one, and in fact when I set the local media file, the note is shown like the following:

fronthttp:///Users/me/Downloads/anki_audio_fr/pronunciation_fr_rapporter.mp3 download failed with error Invalid URL

redstoneleo commented 4 years ago

how can I add the media file to the note?

My solution is

  1. send your audioFile to C:\Users\i\AppData\Roaming\Anki2\User 1\collection.media or other similar locations;
  2. add '[sound:{}]'.format(audioFilename)(including the file extension) to the fields in the note you want.

If one needs to improve AnkiConnect on this feature by coding, I'd suggest reimplementing AnkiConnect's Python 3 version download function by porting the Python 2 version. And if resp.code == 200 should be changed to if resp.code in {200, None}, because urllib.request.urlopen('file:///C:/Users/i/abdominal.spx').code returns None for local file url, in addition, one has also construct a local file url from his audio file path, a convenient way is let AnkiConnect construct it for ourselves, still need implement, this may also means AnkiConnect needs to judge whether a "url" it received in the "audio" field is local or online, this makes me feel there is a need to rename "url" to "audioLocation" for readability .

BTW, the current Python 3 version download function used resp = client.get(url) to download audio files, the mechanism under the hood is requests.get(url) in Anki, while requests does not support local file url scheme by itself, so the only way to let the Python 3 version download function to support local file url scheme is to implement it via urllib.request.

davidf03 commented 4 years ago
  1. send your audioFile to C:\Users\i\AppData\Roaming\Anki2\User 1\collection.media or other similar locations

I'm building a browser extension. Is there still some way to add local files through the media API to cards programmatically? As far as I understand I couldn't have the user set a path outside the downloads folder :/

reuseman commented 3 years ago

If I specify file:///, it says that no connection adapters were found

meteorlin commented 1 year ago

If I specify file:///, it says that no connection adapters were found

If your platform is Windows, you should replace the \ to / or add the r flag in front of the path, e.g.

ori_media_path = "F:\test.mp3"
media_path_1 = "F:/test.mp3"
media_path_2 = r"F:\test.mp3"

There is a way to check whether the "path" parameter is valid or not:

with open(path, 'rb') as f:
    mediaData = f.read()

which exactly is the validation codes and the Exception of "No connection adapters ..." raised in storeMediaFile method. https://github.com/FooSoft/anki-connect/blob/7234914d447583f1f5ed9a335ee8c127e7d35eb0/plugin/__init__.py#L673-L675

ArmelVidali commented 11 months ago

Hi,

After struggling a lot with the same issue, i ended up with this solution :

<audio controls><source src={your_remote_url} type=\"audio/mp3\"></audio>

I was trying to add a remote URL to my anki deck, since i wanted to share it online, it wouldn't work with the [sound:url]you gave (but it did when i stored the audios on my local machine), I wanted a button to click on and start the audio then.

Here is my full script for this :

back_content = 
f"<p>
        {back_sentences[i][0]}<br>
        {back_sentences[i][1]}<br>
        <audio controls>
                <source src={back_sentences[i][2]} type=\"audio/mp3\">
         </audio>
  </p>"

payload = {
        "action": "addNote",
        "version": 6,
        "params": {
            "note": {
                "deckName": deck_name,
                "modelName": "Basic",
                "fields": {
                    "Front": front,
                    "Back": back_content

                },
                "options": {
                    "allowDuplicate": False
                },
            }
        }
    }
    requests.post(ANKI_URL, data=json.dumps(payload))

[sound:{audio_files[i]}] worked when i placed the audios in C:\Users\user_name\AppData\Roaming\Anki2\User 1\collection.media Hope this helps