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/"
)
definitialize_app_checks_and_config():
"""Handles initial application setup including version checks, argument parsing, and configuration loading."""
checkversion(__VERSION__)# This might print, consider replacing if it does. For now, assume it's a simple check or uses logging.
if__name__=="__main__":
parser=argparse.ArgumentParser(description="Reddit Video Maker Bot")
parser.add_argument(
"--list-tts",
@ -89,58 +73,243 @@ if __name__ == "__main__":
args=parser.parse_args()
ifargs.list_tts:
print_step("Available TTS Providers:")
logging.info("Available TTS Providers:")
forproviderinTTSProviders:
print_substep(f"- {provider}")
logging.info(f"- {provider}")# Simple info log for list items
"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."
logging.error(
"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()
ffmpeg_install()
ffmpeg_install()# This function might print, review separately.
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("+"))}'
fromrich.loggingimportRichHandler
console_handler=RichHandler(rich_tracebacks=True,show_path=False,show_time=False,markup=True)# markup=True for rich styles
# 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}")
print("Please install FFmpeg manually and try again.")
exit()
exceptExceptionase:
print(
"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!"
new_subclip.write_videofile(target_video_path,logger='bar'ifsettings.config['settings'].get('verbose_ffmpeg',False)elseNone)# MoviePy's own progress bar
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"
).is_visible():
# This means the post is NSFW and requires to click the proceed button.
print_substep("Post is NSFW. You are spicy...")
page.locator(
"#t3_12hmbug > div > div._3xX726aBn29LDbsDtzr_6E._1Ap4F5maDtT1E1YuCiaO0r.D3IL3FD0RFy_mkKLPwL4 > div > div > button"
).click()
page.wait_for_load_state()# Wait for page to fully load
# translate code
ifpage.locator(
"#SHORTCUT_FOCUSABLE_DIV > div:nth-child(7) > div > div > div > header > div > div._1m0iFpls1wkPZJVo38-LSh > button > i"
).is_visible():
page.locator(
"#SHORTCUT_FOCUSABLE_DIV > div:nth-child(7) > div > div > div > header > div > div._1m0iFpls1wkPZJVo38-LSh > button > i"
).click()# Interest popup is showing, this code will close it
# The selector is very specific and might break easily.
nsfw_button_selector="div > div._3xX726aBn29LDbsDtzr_6E._1Ap4F5maDtT1E1YuCiaO0r.D3IL3FD0RFy_mkKLPwL4 > div > div > button"# Simplified part of original
# A more robust selector might be based on text or a more stable attribute if available.
# Example: page.locator('button:has-text("Yes")') or similar for NSFW confirmation.
# For now, using a part of the original selector. This is fragile.
# 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.
# For this refactor, I'll use a placeholder for a more general NSFW button.
# A better selector would be like: page.locator('[data-testid="content-gate"] button:has-text("View")')
# or page.get_by_role("button", name=re.compile(r"yes|view|proceed", re.IGNORECASE))
# The original selector was extremely brittle.
# Simplified NSFW check (this might need adjustment based on actual Reddit UI)
# 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:
print_substep("Translating post...")
texts_in_tl=translators.translate_text(
reddit_object["thread_title"],
to_language=lang,
translator="google",
)
page.evaluate(
"tl_content => document.querySelector('[data-adclicklocation=\"title\"] > div > div > h1').textContent = tl_content",
texts_in_tl,
)
logger.info(f"Translating post title to '{lang}'...")
try:
texts_in_tl=translators.translate_text(
reddit_object["thread_title"],
to_language=lang,
translator="google",# Consider making translator configurable
)
# This JS evaluation to change content is also brittle.
page.evaluate(
"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:
print_substep("Skipping translation...")
logger.info("Skipping post title translation (no language specified).")