This commit introduces a series of improvements across the codebase:
1. **Logging:**
* Integrated Python's `logging` module throughout the application.
* Replaced custom `print_step/print_substep` functions with standard logging calls (INFO, WARNING, ERROR, DEBUG).
* Configured file logging (rotating logs) and console logging (using RichHandler if available).
* Enhanced FFmpeg error logs to include commands and stderr.
2. **Error Handling:**
* Made error handling more robust in TTS modules, Playwright interactions (screenshotting), API calls, and file operations.
* Replaced `sys.exit()` calls in modules with exceptions to allow better control by the caller.
* Improved reporting of unhandled exceptions in `main.py` with tracebacks to the log file.
3. **Code Quality & Modularity:**
* Refactored `main.py` and `video_creation/final_video.py` into smaller, more manageable functions, reducing global state and improving readability.
* Replaced `eval()` in configuration parsing (`utils/settings.py`, `utils/gui_utils.py`) with safer type conversion methods.
* Decoupled background choice management from the static TOML template, now dynamically loaded from `backgrounds.json`.
* Standardized path handling using `pathlib.Path` across numerous modules, replacing `os.path` and manual string concatenations.
* Corrected OS dispatcher logic in `utils/ffmpeg_install.py`.
* Clarified voice selection logic in `TTS/pyttsx.py`.
4. **FFmpeg Interaction:**
* Reviewed and refined the `ProgressFfmpeg` class for more robust progress parsing and thread management.
5. **Testing Groundwork:**
* Created `tests/` directory and added initial unit tests for utility functions `name_normalize` and `_safe_str_to_bool` using `unittest`.
6. **Documentation:**
* Added/updated module-level and key function docstrings in `main.py` and `video_creation/final_video.py`.
"### Thanks for using this tool! Feel free to contribute to this project on GitHub! If you have any questions, feel free to join my Discord server or submit a GitHub issue. You can find solutions to many common problems in the documentation: https://reddit-video-maker-bot.netlify.app/"
"### Thanks for using this tool! Feel free to contribute to this project on GitHub! If you have any questions, feel free to join my Discord server or submit a GitHub issue. You can find solutions to many common problems in the documentation: https://reddit-video-maker-bot.netlify.app/"
"Hey! Congratulations, you've made it so far (which is pretty rare with no Python 3.10). Unfortunately, this program only works on Python 3.10. Please install Python 3.10 and try again."
"Hey! Congratulations, you've made it so far (which is pretty rare with Python 3.10/3.11). "
"Unfortunately, this program primarily supports Python 3.10 and 3.11. "
"Please install one of these versions and try again."
)
)
sys.exit()
sys.exit()
ffmpeg_install()
ffmpeg_install()# This function might print, review separately.
console_handler=RichHandler(rich_tracebacks=True,show_path=False,show_time=False,markup=True)# markup=True for rich styles
index+=1
console_handler.setLevel(logging.INFO)
print_step(
log_formatter=logging.Formatter("%(message)s")
f'on the {index}{("st"ifindex%10==1else("nd"ifindex%10==2else("rd"ifindex%10==3else"th")))} post of {len(config["reddit"]["thread"]["post_id"].split("+"))}'
# logging.error(f"Sorry, something went wrong with this version!\nVersion: {__VERSION__}\nError: {err}\nConfig (sensitive fields redacted): {config_settings_str}")
# Re-raise if you want Python's default exception printing to also occur,
# or if something else higher up should handle it.
# For a CLI app, often we log and then exit.
shutdown_app()# Ensure cleanup and exit
finally:
if_current_reddit_id_for_cleanup:
logging.info(f"Performing final cleanup for ID: {_current_reddit_id_for_cleanup}")
raiseFileNotFoundError("FFmpeg not found and user declined installation.")
exceptExceptionase:
print(
exceptsubprocess.CalledProcessErrorase:
"Welcome fellow traveler! You're one of the few who have made it this far. We have no idea how you got at this error, but we're glad you're here. Please report this error to the developer, and we'll try to fix it as soon as possible. Thank you for your patience!"
# This means ffmpeg -version returned non-zero, which is unusual but possible.
)
logger.warning(f"FFmpeg check command 'ffmpeg -version' executed but returned an error (code {e.returncode}). FFmpeg might have issues.")
print_substep("Background video chopped successfully!",style="bold green")
new_subclip.write_videofile(target_video_path,logger='bar'ifsettings.config['settings'].get('verbose_ffmpeg',False)elseNone)# MoviePy's own progress bar
returnbackground_config["video"][2]
exceptExceptionasmoviepy_e:
logger.error(f"MoviePy subclip method also failed: {moviepy_e}",exc_info=True)
raiseRuntimeError(f"Both ffmpeg_extract_subclip and MoviePy subclip failed for {video_source_path}")
logger.info("Background video chopped successfully!")
"#t3_12hmbug > div > div._3xX726aBn29LDbsDtzr_6E._1Ap4F5maDtT1E1YuCiaO0r.D3IL3FD0RFy_mkKLPwL4 > div > div > button"
"#t3_12hmbug > div > div._3xX726aBn29LDbsDtzr_6E._1Ap4F5maDtT1E1YuCiaO0r.D3IL3FD0RFy_mkKLPwL4 > div > div > button"
).is_visible():
).is_visible():
# This means the post is NSFW and requires to click the proceed button.
# This means the post is NSFW and requires to click the proceed button.
# The selector is very specific and might break easily.
print_substep("Post is NSFW. You are spicy...")
nsfw_button_selector="div > div._3xX726aBn29LDbsDtzr_6E._1Ap4F5maDtT1E1YuCiaO0r.D3IL3FD0RFy_mkKLPwL4 > div > div > button"# Simplified part of original
page.locator(
# A more robust selector might be based on text or a more stable attribute if available.
"#t3_12hmbug > div > div._3xX726aBn29LDbsDtzr_6E._1Ap4F5maDtT1E1YuCiaO0r.D3IL3FD0RFy_mkKLPwL4 > div > div > button"
# Example: page.locator('button:has-text("Yes")') or similar for NSFW confirmation.
).click()
# For now, using a part of the original selector. This is fragile.
page.wait_for_load_state()# Wait for page to fully load
# This specific selector "#t3_12hmbug > ..." is tied to a post ID and will not work generally.
# A more general approach is needed, perhaps looking for buttons with "yes" or "proceed" text within a modal.
# translate code
# For this refactor, I'll use a placeholder for a more general NSFW button.
ifpage.locator(
# A better selector would be like: page.locator('[data-testid="content-gate"] button:has-text("View")')
"#SHORTCUT_FOCUSABLE_DIV > div:nth-child(7) > div > div > div > header > div > div._1m0iFpls1wkPZJVo38-LSh > button > i"
# or page.get_by_role("button", name=re.compile(r"yes|view|proceed", re.IGNORECASE))
).is_visible():
# The original selector was extremely brittle.
page.locator(
"#SHORTCUT_FOCUSABLE_DIV > div:nth-child(7) > div > div > div > header > div > div._1m0iFpls1wkPZJVo38-LSh > button > i"
# Simplified NSFW check (this might need adjustment based on actual Reddit UI)
).click()# Interest popup is showing, this code will close it
# Try to find a common NSFW confirmation button
# This is a guess, actual selector might be different:
nsfw_proceed_button=page.locator('button:has-text("View")').or_(page.locator('button:has-text("Yes, I am over 18")'))
# For now, skipping this as the selector is too unreliable.
# logger.debug("Checking for interest popup...")
iflang:
iflang:
print_substep("Translating post...")
logger.info(f"Translating post title to '{lang}'...")
texts_in_tl=translators.translate_text(
try:
reddit_object["thread_title"],
texts_in_tl=translators.translate_text(
to_language=lang,
reddit_object["thread_title"],
translator="google",
to_language=lang,
)
translator="google",# Consider making translator configurable
)
page.evaluate(
# This JS evaluation to change content is also brittle.
"tl_content => document.querySelector('[data-adclicklocation=\"title\"] > div > div > h1').textContent = tl_content",
page.evaluate(
texts_in_tl,
"tl_content => { try { document.querySelector('[data-adclicklocation=\"title\"] > div > div > h1').textContent = tl_content; } catch(e){ console.error('Failed to set title via JS:', e); } }",
)
texts_in_tl,
)
logger.info("Post title translation applied via JS (if element found).")
exceptExceptionase:
logger.warning(f"Failed to translate post title or apply it: {e}")
else:
else:
print_substep("Skipping translation...")
logger.info("Skipping post title translation (no language specified).")