pyttsx3 text to speech

I am relatively new to django and am developing a flashcard app with text to speech using pyttsx3 engine. I have coded the flashcard to translate the speech from both sides of the card however I am not able to get kit to work. I tested the engine with a small engine and it works I inspected the code in the browser element and it says SyntaxError: Unexpected token ‘<’, "<!DOCTYPE "… is not valid JSON
I understand this error has something to do with html being passed but not json but cant find the cause of the error in my code.

{% extends "base.html" %}
{% load static %}

{% block content %}
<div class="container">
    <div class="row">
        {% for card in list_of_cards %}
        <div class="rehearse_card" id="card-{{ card.id }}">
            <div class="card text-center" style="width: 30rem;">
                <img src="{% static 'images/FlashCardImage.svg' %}" class="card-img-top" alt="...">
                <div class="card-body">
                    <div class="card-front">
                        <h4 class="card-title-question">Q: {{ card.question }}</h4>
                        <button class="btn btn-primary btn-sm" onclick="flipCard('{{ card.id }}')">Flip Card</button>
                        <button class="btn btn-secondary btn-sm tts-button" data-card-id="{{ card.id }}"
                            data-question-audio="{{ card.question_audio.url }}">Text to Speech (Question)</button>
                    </div>
                    <div class="card-back" style="display: none;">
                        <h5 class="card-text-answer">A: {{ card.answer }}</h5>
                        <button class="btn btn-secondary btn-sm tts-button" data-card-id="{{ card.id }}"
                            data-answer-audio="{{ card.answer_audio.url }}">Text to Speech (Answer)</button>
                    </div>
                </div>
                <div class="card-footer">
                    <small class="text-muted">{{ card.id }} {{ card.deck.title }} · {{ card.deck.subject }}</small>
                </div>
            </div>
        </div>
        {% endfor %}
    </div>
    </div>
    </div>
</div>

<script>
    function flipCard(cardId) {
        const cardFront = document.querySelector(`#card-${cardId} .card-front`);
        const cardBack = document.querySelector(`#card-${cardId} .card-back`);

        if (cardFront && cardBack) {
            cardFront.style.display = cardFront.style.display === 'none' ? 'block' : 'none';
            cardBack.style.display = cardBack.style.display === 'none' ? 'block' : 'none';
        } else {
            console.error('Card elements not found.');
        }
    }

    function toggleTextToSpeech(button) {
        const cardId = button.dataset.cardId;
        const deckId = window.location.pathname.split('/')[3]; // Assuming the deck_id is the 4th part of the URL path
        const questionAudioUrl = button.dataset.questionAudio;
        const answerAudioUrl = button.dataset.answerAudio;

        const url = `/deck/rehearse/${deckId}/text-to-speech/${cardId}/`;

        fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': '{{ csrf_token }}',
            },
            body: JSON.stringify({}),
        })
        .then(response => response.json())
        .then(data => {
            const questionAudio = new Audio(`data:audio/mpeg;base64,${data.question_audio}`);
            const answerAudio = new Audio(`data:audio/mpeg;base64,${data.answer_audio}`);

            questionAudio.play();
            questionAudio.addEventListener('ended', () => {
                answerAudio.play();
            });
        })
        .catch(error => {
            console.error('Error:', error);
        });
    }

    const ttsButtons = document.querySelectorAll('.tts-button');
    ttsButtons.forEach(button => {
        button.addEventListener('click', () => toggleTextToSpeech(button));
    });
</script>
{% endblock %}

views.py

import os
import base64
import pyttsx3
from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from .models import Card, Deck, StudySet, StudentDeck
from .forms import CardFormSet, DeckForm
from django.views.generic.edit import CreateView
from django.contrib.auth import authenticate, login
from django.contrib.auth.forms import AuthenticationForm


from django.views.decorators.http import require_POST

from django.http import JsonResponse
from django.shortcuts import get_object_or_404
import random

def text_to_speech(request, deck_id, card_id=None):
    deck = get_object_or_404(Deck, id=deck_id)
    cards = deck.card_set.all()

    if not cards:
        return JsonResponse({'error': 'No cards found for this deck.'}, status=404)

    if card_id:
        card = get_object_or_404(Card, id=card_id)
    else:
        card = random.choice(cards)

    engine = pyttsx3.init()

    # Generate audio data for the question
    question_audio_path = os.path.join(settings.MEDIA_ROOT, 'audio', f'question_{card.id}.mp3')
    engine.save_to_file(card.question, question_audio_path)
    engine.runAndWait()
    with open(question_audio_path, 'rb') as f:
        question_audio_base64 = base64.b64encode(f.read()).decode('utf-8')
    os.remove(question_audio_path)

    # Generate audio data for the answer
    answer_audio_path = os.path.join(settings.MEDIA_ROOT, 'audio', f'answer_{card.id}.mp3')
    engine.save_to_file(card.answer, answer_audio_path)
    engine.runAndWait()
    with open(answer_audio_path, 'rb') as f:
        answer_audio_base64 = base64.b64encode(f.read()).decode('utf-8')
    os.remove(answer_audio_path)

    audio_data = {
        'question_audio': question_audio_base64,
        'answer_audio': answer_audio_base64,
    }

    return JsonResponse(audio_data)

def home(request):
    if request.user.is_authenticated:
        return redirect('all_decks')
    else:
        if request.method == 'POST':
            form = AuthenticationForm(request, data=request.POST)
            if form.is_valid():
                user = authenticate(username=form.cleaned_data['username'], password=form.cleaned_data['password'])
                if user is not None:
                    login(request, user)
                    return redirect('home')
        else:
            form = AuthenticationForm()
        return render(request, 'login.html', {'form': form})


def show_all_cards(request):
    c_list = Card.objects.all()
    context = {'list_of_cards': c_list}
    return render(request, 'show_all_cards.html', context)


def delete_card(request, card_id):
    card_to_delete = Card.objects.get(id=card_id)
    card_to_delete.delete()
    return redirect('show_all_cards')


def show_all_decks(request):
    d_list = Deck.objects.all()
    context = {'list_of_decks': d_list}
    return render(request, 'all_decks.html', context)


def delete_deck(request, deck_id):
    deck_to_delete = Deck.objects.get(id=deck_id)
    deck_to_delete.delete()
    return redirect('all_decks')


class DeckCreate(CreateView):
    form_class = DeckForm
    model = Deck

    def form_valid(self, form):
        f = form.save(commit=False)
        f.creator = self.request.user
        f.save()
        return super().form_valid(form)


def generate_audio(text, output_path):
    engine = pyttsx3.init()
    engine.save_to_file(text, output_path)
    engine.runAndWait()


def create_cards(request, deck_id):
    deck = get_object_or_404(Deck, pk=deck_id)

    if request.method == 'POST':
        formset = CardFormSet(request.POST, instance=deck)
        if formset.is_valid():
            cards = formset.save(commit=False)
            for card in cards:
                card.deck = deck

                # Generate audio files for the question and answer
                question_audio_path = os.path.join(settings.MEDIA_ROOT, 'audio', f'question_{card.id}.mp3')
                answer_audio_path = os.path.join(settings.MEDIA_ROOT, 'audio', f'answer_{card.id}.mp3')

                generate_audio(card.question, question_audio_path)
                generate_audio(card.answer, answer_audio_path)

                card.question_audio = os.path.join('audio', f'question_{card.id}.mp3')
                card.answer_audio = os.path.join('audio', f'answer_{card.id}.mp3')

                card.save()

            return redirect('create_cards', deck_id=deck_id)

    else:
        formset = CardFormSet(instance=deck)

    return render(request, 'create_cards.html', {'formset': formset, 'deck': deck})


def show_deck(request, deck_id):
    deck = get_object_or_404(Deck, pk=deck_id)
    c_list = Card.objects.filter(deck=deck)
    context = {'list_of_cards': c_list, 'deck': deck}
    return render(request, 'show_deck.html', context)


def rehearse_deck(request, deck_id):
    deck = get_object_or_404(Deck, pk=deck_id)
    c_list = Card.objects.filter(deck=deck)
    context = {
        'list_of_cards': c_list,
        'deck': deck,
        'audio_enabled': request.GET.get('audio_enabled', False)
    }
    return render(request, 'rehearse_deck.html', context)


def save_for_study(request, deck_id):
    user = request.user
    deck = get_object_or_404(Deck, pk=deck_id)
    studyset, created = StudySet.objects.get_or_create(student=user)
    if not StudentDeck.objects.filter(deck=deck, student=user).exists():
        s = StudentDeck()
        s.studyset = studyset
        s.student = user
        s.deck = deck
        s.save()
    return redirect('show_my_studyset')


def show_my_studyset(request):
    studyset = StudySet.objects.get(student_id=request.user.id)
    deck_list = StudentDeck.objects.filter(studyset_id=studyset.id)
    context = {'studyset': studyset, 'deck_list': deck_list}
    return render(request, 'my_studyset.html', context)


def remove_from_studyset(request, studentdeck_id):
    deck_to_remove = get_object_or_404(StudentDeck, id=studentdeck_id)
    deck_to_remove.delete()
    return redirect('show_my_studyset')


def leitner(request, deck_id):
    deck = get_object_or_404(Deck, pk=deck_id)
    c_list = Card.objects.filter(deck=deck)
    context = {'list_of_cards': c_list}
    return render(request, 'leitner_deck.html', context)

This error is typically caused by a 404 on the request.

Please show what the url is that is being requested, and what that url is supposed to return. Also please post any errors that may be showing up on the server console.
(Is this occurring in a development environment or your production environment?)

I am currently still developing the application
url file

from django.urls import path
from . import views
from .views import DeckCreate
from django.conf import settings  # Import the settings module
from django.conf.urls.static import static  # Import the static function
from django.urls import path  # Import the path function
from django.contrib import admin  # 

urlpatterns = [ 
    #path('', views.home, name='home'),
    path('', views.show_all_decks, name='all_decks'),
    path('card/all', views.show_all_cards, name='all_cards'),
    path('cards/', views.show_all_cards, name='all_cards'),
    path('cards/delete/<int:card_id>/', views.delete_card, name='delete_card'),
    path('decks/', views.show_all_decks, name='all_decks'),
    path('deck/<deck_id>/', views.create_cards, name='create_cards'),
    path('deck/delete/<int:deck_id>/', views.delete_deck, name='delete_deck'),
    path('decks/create/', DeckCreate.as_view(), name='create_deck'),
    path('deck/<deck_id>/', views.create_cards, name='create_cards'),
    path('deck/show/<int:deck_id>/', views.show_deck, name="show_deck"),
    path('deck/rehearse/<int:deck_id>/', views.rehearse_deck, name="rehearse_deck"),
    path('deck/save/<int:deck_id>/', views.save_for_study, name="save_for_study"),
    path('studyset/', views.show_my_studyset, name='show_my_studyset'),
    path('studyset/remove/<int:studentdeck_id>/',views.remove_from_studyset, name= 'remove_from_studyset'),
    path('deck/leitner/<int:deck_id>', views.leitner, name='leitner'),
    path('deck/rehearse/<int:deck_id>/text-to-speech/<int:card_id>', views.text_to_speech, name='text_to_speech'),

   
    
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

This is the response I am getting from the server error message

Django version 4.1.13, using settings 'languages.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[03/Jun/2024 06:33:38] "GET / HTTP/1.1" 200 7611
Not Found: /favicon.ico
[03/Jun/2024 06:33:38] "GET /favicon.ico HTTP/1.1" 404 5450
[03/Jun/2024 06:39:46] "GET /deck/show/22/ HTTP/1.1" 200 13859
[03/Jun/2024 06:39:49] "GET /deck/rehearse/22/ HTTP/1.1" 200 30430
Not Found: /deck/rehearse/22/text-to-speech/5/
[03/Jun/2024 06:39:52] "POST /deck/rehearse/22/text-to-speech/5/ HTTP/1.1" 404 5542

Not Found: /deck/rehearse/22/text-to-speech/5/
[03/Jun/2024 06:39:52] “POST /deck/rehearse/22/text-to-speech/5/ HTTP/1.1” 404 5542

That’s the request creating the error.

This shows that that URL will call the text_to_speech view.

This is the start of that view - one way that you’ll get a 404 is if you don’t have a Deck with id == 22.

Later on in that view, you have:

This will generate a 404 if you don’t have a Card with id == 5.

Since you’re not showing any other error, it seems most likely that one of those two conditions are false.

It seems that the code cannot find the cards id. What would you suggest would be a way of debugging this problem, all of the cards exist?

Please be more specific by what you mean by this.

Do you have an instance of Card where its id is 5?

For this instance of card that you mentioned would I have to check the sql3 database in django?

You could use the Django shell or dbshell, it doesn’t matter which. Or, if you’ve got the admin configured for it, you could use that. Or you could use the dumpdata command and look at the output. The tool doesn’t matter, you just want some way to validate what’s in your database.

I used SQL onLine IDE to show the cards DB and according to the DB all the cards are correct and card 5 does exist as you can see from screenshot

Ahhh, I think I see it. The url that you are submitting has a trailing ‘/‘, but your url definition does not. I suggest you ensure all urls contain a trailing ‘/‘.

Perhaps you could be more specific are you talking about the url.py file?

Yes, i’m referring to your path definitions in your urls.py file.

This option has not worked this is the error i get if i add slash

WARNINGS:
?: (urls.W002) Your URL pattern ‘^/$’ [name=‘all_decks’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/card/all/$’ [name=‘all_cards’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/cards/$’ [name=‘all_cards’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/cards/delete/(?P<card_id>\d+)/$’ [name=‘delete_card’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/deck/(?P<deck_id>\d+)/$’ [name=‘create_cards’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/deck/(?P<deck_id>\d+)/$’ [name=‘create_cards’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/deck/delete/(?P<deck_id>\d+)/$’ [name=‘delete_deck’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/deck/leitner/(?P<deck_id>\d+)/$’ [name=‘leitner’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/deck/rehearse/(?P<deck_id>\d+)/$’ [name=‘rehearse_deck’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/deck/rehearse/(?P<deck_id>\d+)/text-to-speech/(?P<card_id>\d+)/$’ [name=‘text_to_speech’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/deck/save/(?P<deck_id>\d+)/$’ [name=‘save_for_study’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/deck/show/(?P<deck_id>\d+)/$’ [name=‘show_deck’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/decks/$’ [name=‘all_decks’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/decks/create/$’ [name=‘create_deck’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/studyset/$’ [name=‘show_my_studyset’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.
?: (urls.W002) Your URL pattern ‘^/studyset/remove/(?P<studentdeck_id>\d+)/$’ [name=‘remove_from_studyset’] has a route beginning with a ‘/’. Remove this slash as it is unnecessary. If this pattern is targeted in an include(), ensure the include() pattern has a trailing ‘/’.

System check identified 16 issues (0 silenced).
June 05, 2024 - 14:48:21
Django version 5.0.6, using settings ‘languages.settings’
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

Not Found: /
[05/Jun/2024 14:48:59] “GET / HTTP/1.1” 404 5479
Not Found: /
[05/Jun/2024 14:49:28] “GET / HTTP/1.1” 404 5479
Not Found: /
[05/Jun/2024 14:49:34] “GET / HTTP/1.1” 404 5479

I also tried the urls with a true parameter and got this error

File “C:\Users\botan\Desktop\project\languages\cardDesigner\urls.py”, line 9, in
path(‘’, views.show_all_decks, name=‘all_decks’, trailing_slash=True),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: _path() got an unexpected keyword argument ‘trailing_slash’

I have now solved this problem of the url pages by placing in the settings.py
MEDIA_URL = ‘/media/’ however when selecting the text to speech button no text to speech operates in the app.

[Emphasis added]

In other words, a url like:

should become:

path('card/all/', views.show_all_cards, name='all_cards'),
              ^ This is a trailing slash

I checked the urls that do do not have the backtick only two of them have. I am still getting this error
[06/Jun/2024 11:07:36] “POST /deck/rehearse/22/text-to-speech/5/ HTTP/1.1” 404 5642
Not Found: /deck/rehearse/22/text-to-speech/5/
[06/Jun/2024 11:08:05] “POST /deck/rehearse/22/text-to-speech/5/ HTTP/1.1” 404 5642
I dont get the text to speech audio when the button is selected.

Is the urls.py file you’re showing here your root urls.py file? (The one referenced by your settings.py file.) If it is not, please post that urls.py file.

The root urls.py file referenced by the settings.py file is languages/languages/urls.py .

os.environ.setdefault(‘DJANGO_SETTINGS_MODULE’, ‘languages.settings’)

Please confirm that this is the urls.py file that you posted at the top.

This is the root path url

path(‘’, views.show_all_decks, name=‘all_decks’),