debugpythonModerate
Storytelling program: Gathers stories from certain popular story websites/formats and reads them aloud
Viewed 0 times
storiesgatherspopularwebsitescertainprogramstorystorytellingreadsformats
Problem
Audiobooks make my life easier when I have idle ears and a mind free to wander, but something better for my eyes to do.
This program is one of my more recent efforts to allow most of the stories written on popular websites to be transformed into a more accessible audio format so that they can be enjoyed without having to steal the important function of vision from the user.
Concerns:
-
Primary Concerns:
-
Actual usefulness of program
-
Can I clean up the code in any further way so as to make it easier to read?
-
How the program holds up to real-world inputs from various sources and how to handle an unexpected error
-
Secondary Concerns:
-
Making it more convenient to use
-
Cross platform compatibility (Possibly)
-
Dealing with inputs I have not accounted for (URLs with no '
-
Tertiary Concerns:
-
Making sure I have formatted my code for readability.
-
Are there any parts of my code that need more explanation?
Core Components:
-
Automated required module retrieval
-
-
-
Pros:
-
You can listen to stories as they are being downloaded by navigating to your
-
The succeeding chapters in a fanfiction.net story will be downloaded in preparation. (Is this something that I should remove?)
Cons:
-
If a URL or path is not formatted correctly for the program to read, it will treat that URL or path as text and will instead read it out loud.
-
Will spit out large blocks of story-text. (Would it be
This program is one of my more recent efforts to allow most of the stories written on popular websites to be transformed into a more accessible audio format so that they can be enjoyed without having to steal the important function of vision from the user.
Concerns:
-
Primary Concerns:
-
Actual usefulness of program
-
Can I clean up the code in any further way so as to make it easier to read?
-
How the program holds up to real-world inputs from various sources and how to handle an unexpected error
-
Secondary Concerns:
-
Making it more convenient to use
-
Cross platform compatibility (Possibly)
-
Dealing with inputs I have not accounted for (URLs with no '
http://' in them)-
Tertiary Concerns:
-
Making sure I have formatted my code for readability.
-
Are there any parts of my code that need more explanation?
Core Components:
-
Automated required module retrieval
- Will fetch required modules if they are not installed on the system
-
say() Function- Uses Google's Text-to-Speech engine if online to read text and a local solution using Microsoft's built in Text-to-Speech engine if offline
-
getStory Class- Handles the retrieval and classification of text from the various websites or formats
-
OmniReader Function- Handles how to output the text from these websites or formats
Pros:
-
You can listen to stories as they are being downloaded by navigating to your
C:\Users\'Username' directory, where an MP3 will be stored containing your story name and chapter number.-
The succeeding chapters in a fanfiction.net story will be downloaded in preparation. (Is this something that I should remove?)
Cons:
-
If a URL or path is not formatted correctly for the program to read, it will treat that URL or path as text and will instead read it out loud.
-
Will spit out large blocks of story-text. (Would it be
Solution
I'm just going to review these eight lines of code. You'll see that there's plenty here for one answer.
-
If you restricted your lines to 79 columns, as recommended by the Python style guide (PEP8), then we wouldn't have to scroll the code horizontally to read it here.
-
The prompt is also more than 79 columns wide, which means that on most users' terminals it will wrap. Better to split this prompt:
or use
-
A bare
-
The test:
is always true! That's because it parses like this:
and
-
The test
-
If the user chooses to install the gTTS package, the program does not make a second attempt to import it. So the user will be disappointed: it will appear that the installation was ineffective.
-
Running the command
-
-
After a failure to import, this code prints a warning, but then just carries on. This will just lead to a
-
These eight lines of code are essentially repeated three times for three different modules. It would be better to extract the common code and make it into a function. This is slightly tricky because of the need to import a module given its name as a string, but we can use
Now it's possible to write:
Note that this still has an
try:
from gtts import gTTS
except:
answer = input("Your system does not have Google's Text to Speech API installed. Do you want to install it?")
if 'y' or 'Y' in answer:
os.system('python -m pip install --upgrade gTTS')
else:
print("Without the Google Text to Speech API, this process will not sound natural.")-
If you restricted your lines to 79 columns, as recommended by the Python style guide (PEP8), then we wouldn't have to scroll the code horizontally to read it here.
-
The prompt is also more than 79 columns wide, which means that on most users' terminals it will wrap. Better to split this prompt:
print("Your system does not have Google's Text to Speech API installed.")
answer = input("Do you want to install it?")or use
textwrap.fill to split it into lines.-
A bare
except: is a bad idea: it catches all exceptions, including KeyboardInterrupt and SystemExit. This means that you might suppress genuine problems that you would have preferred to know about. It is better to catch just the exception that you are interested in, which is ImportError in this case.-
The test:
if 'y' or 'Y' in answer:is always true! That's because it parses like this:
if 'y' or ('Y' in answer):and
'y' is a non-empty string, so it converts to true. The code needs to say:if 'y' in answer or 'Y' in answer:-
The test
'y' in answer accepts too many strings, I think. If the user enters nay or no way or not on your life then this test will still evaluate to true. Better to be more restrictive, for example answer.startswith(('Y', 'y')).-
If the user chooses to install the gTTS package, the program does not make a second attempt to import it. So the user will be disappointed: it will appear that the installation was ineffective.
-
Running the command
python relies on the Python interpreter being on PATH, and also on this Python interpreter being the right one in the common case where there are several installed versions of Python on the same machine. It would be better to use sys.executable.-
os.system can be wasteful of resources (because it starts a shell to run the command) and risky (because the command gets parsed by the shell and this can go wrong). In this case neither of these problems is severe, but it's worth practicing good security habits in easy situations, so I recommend subprocess.call instead.-
After a failure to import, this code prints a warning, but then just carries on. This will just lead to a
NameError later on when the program tries to evaluate gTTS. It would be better to exit the program, for example by re-raising the exception.-
These eight lines of code are essentially repeated three times for three different modules. It would be better to extract the common code and make it into a function. This is slightly tricky because of the need to import a module given its name as a string, but we can use
importlib.import_module, like this:from importlib import import_module
import subprocess
import sys
from textwrap import fill
def import_or_install(module, package, description, capability):
"""Import module. If it fails to import, prompt user to install
package and try again. The description argument gives a brief
human-readable description of the module, and capability gives a
human-readable description of what it can do.
"""
try:
import_module(module)
except ImportError:
print(fill("Your system does not have {} installed. "
"Without this module, this process will not {}."
.format(description, capability)))
answer = input("Do you want to install it?")
if answer.startswith(('Y', 'y')):
subprocess.call([sys.executable, '-m', 'pip', 'install',
'--upgrade', package])
import_module(module)
else:
raiseNow it's possible to write:
import_or_install('gtts', 'gTTS', 'Google Text to Speech API',
'sound natural')
from gtts import gTTS
import_or_install('bs4', 'beautifulsoup4', 'Beautiful Soup',
'be able to pull text from websites'):
from bs4 import BeautifulSoup
import_or_install('PyPDF2', 'PyPDF2', 'PyPDF2',
'be able to process PDF files'):
import PyPDF2Note that this still has an
import statement after calling import_or_install because we need to assign a name to the module. Calling importlib.import_module loads the module (and puts an entry in sys.modules), but does not actually assign it a name.Code Snippets
try:
from gtts import gTTS
except:
answer = input("Your system does not have Google's Text to Speech API installed. Do you want to install it?")
if 'y' or 'Y' in answer:
os.system('python -m pip install --upgrade gTTS')
else:
print("Without the Google Text to Speech API, this process will not sound natural.")print("Your system does not have Google's Text to Speech API installed.")
answer = input("Do you want to install it?")if 'y' or 'Y' in answer:if 'y' or ('Y' in answer):if 'y' in answer or 'Y' in answer:Context
StackExchange Code Review Q#95639, answer score: 10
Revisions (0)
No revisions yet.