Trigger playback via Python or CLI?
Hi, is there a way to run this with playback via Python or via CLI?
Like:
pymacrorecord.playback(PATH_TO_JSON)
or
PyMacroRecord_1,3,0-portable.exe PATH_TO_JSON
If so, please let me know and I'll donate to the project!
Hello, no it's not implemented and it's not planned unfortunately. PyMacroRecord is on stand-by at the moment because I'm working on another big project and I don't have much time to dedicate time to PyMacroRecord.
Let me know if I should PR. I uploaded your code to Gemini and got a working version that allows for PyMacroRecord-portable.exe test.pmr, just need to change main_app.py and build:
import json
import sys
from tkinter import *
from tkinter import messagebox # Added messagebox import
from os import path
from utils.not_windows import NotWindows
from windows.window import Window
from windows.main.menu_bar import MenuBar
from utils.user_settings import UserSettings
from utils.get_file import resource_path
from utils.warning_pop_up_save import confirm_save
from utils.record_file_management import RecordFileManagement
from utils.version import Version
from windows.others.new_ver_avalaible import NewVerAvailable
from hotkeys.hotkeys_manager import HotkeysManager
from macro import Macro
from sys import platform, argv
from pystray import Icon, MenuItem
from PIL import Image
from threading import Thread
from json import load # json.load is used directly
from time import time
import copy
if platform.lower() == "win32":
from tkinter.ttk import *
def deepcopy_dict_missing_entries(dst: dict, src: dict):
# recursively copy entries that are in src but not in dst
for k, v in src.items():
if k not in dst:
dst[k] = copy.deepcopy(v)
elif isinstance(v, dict):
deepcopy_dict_missing_entries(dst[k], v)
class MainApp(Window):
"""Main windows of the application"""
def __init__(self):
super().__init__("PyMacroRecord", 350, 200)
self.attributes("-topmost", 1)
if platform == "win32":
self.iconbitmap(resource_path(path.join("assets", "logo.ico")))
self.settings = UserSettings(self)
self.load_language()
# For save message purpose
self.macro_saved = False
self.macro_recorded = False
self.current_file = None
self.prevent_record = False
self.version = Version(self.settings.get_config(), self)
self.menu = MenuBar(self) # Menu Bar
self.macro = Macro(self)
self.validate_cmd = self.register(self.validate_input)
self.hotkeyManager = HotkeysManager(self)
self.status_text = Label(self, text="", relief=SUNKEN, anchor=W)
if self.settings.get_config()["Recordings"]["Show_Events_On_Status_Bar"]:
self.status_text.pack(side=BOTTOM, fill=X)
# Initialize image assets
self.playImg = PhotoImage(
file=resource_path(path.join("assets", "button", "play.png"))
)
self.recordImg = PhotoImage(
file=resource_path(path.join("assets", "button", "record.png"))
)
self.stopImg = PhotoImage(
file=resource_path(path.join("assets", "button", "stop.png"))
)
# Initialize center frame for buttons
self.center_frame = Frame(self)
self.center_frame.pack(expand=True, fill=BOTH)
# Create Play and Record buttons - their commands might be updated by CLI logic
self.playBtn = Button(self.center_frame, image=self.playImg)
self.playBtn.pack(side=LEFT, padx=50)
self.recordBtn = Button(
self.center_frame, image=self.recordImg, command=self.macro.start_record
)
self.recordBtn.pack(side=RIGHT, padx=50)
cli_playback_initiated = False
# Handle command-line argument for auto-playback
if len(argv) > 1:
file_path_arg = argv[1]
if path.isfile(file_path_arg) and (
file_path_arg.lower().endswith(".pmr")
or file_path_arg.lower().endswith(".json")
):
try:
self.update_idletasks() # Ensure UI is somewhat ready
with open(file_path_arg, "r") as record_file_content:
loaded_content = load(record_file_content)
self.macro.import_record(loaded_content)
self.macro_recorded = True
self.current_file = file_path_arg
self.macro_saved = True # Considered saved as it's loaded
self.playBtn.configure(
command=self.macro.start_playback
) # Configure command
self.macro.start_playback() # Automatically start playback
cli_playback_initiated = True
except FileNotFoundError:
messagebox.showerror(
"Error", f"Macro file not found: {file_path_arg}"
)
except json.JSONDecodeError:
messagebox.showerror(
"Error", f"Error decoding JSON from macro file: {file_path_arg}"
)
except Exception as e:
messagebox.showerror(
"Error",
f"Failed to load or play macro: {e}\nFile: {file_path_arg}",
)
elif not (
file_path_arg.lower().endswith(".pmr")
or file_path_arg.lower().endswith(".json")
):
messagebox.showwarning(
"PyMacroRecord",
f"Invalid file type: {file_path_arg}\nPlease provide a .pmr or .json file.",
)
else: # File does not exist
messagebox.showwarning(
"PyMacroRecord", f"File not found: {file_path_arg}"
)
# Set default playBtn command if not handled by CLI playback
if not cli_playback_initiated:
self.playBtn.configure(
command=self.macro.start_playback if self.macro_recorded else None
)
record_management = RecordFileManagement(self, self.menu)
self.bind("<Control-Shift-S>", record_management.save_macro_as)
self.bind("<Control-s>", record_management.save_macro)
self.bind("<Control-l>", record_management.load_macro)
self.bind("<Control-n>", record_management.new_macro)
self.protocol("WM_DELETE_WINDOW", self.quit_software)
if platform.lower() != "darwin":
Thread(target=self.systemTray).start()
self.attributes("-topmost", 0)
if platform != "win32" and self.settings.first_time:
NotWindows(self)
if self.settings.get_config()["Others"]["Check_update"]:
if (
self.version.new_version != ""
and self.version.version != self.version.new_version
):
if time() > self.settings.get_config()["Others"]["Remind_new_ver_at"]:
NewVerAvailable(self, self.version.new_version)
self.mainloop()
def load_language(self):
self.lang = self.settings.get_config()["Language"]
# Make sure text_content is initialized even if file loading fails
self.text_content = {}
try:
with open(
resource_path(path.join("langs", self.lang + ".json")), encoding="utf-8"
) as f:
loaded_lang_data = json.load(f)
self.text_content = loaded_lang_data.get(
"content", {}
) # Use .get for safety
if self.lang != "en":
with open(
resource_path(path.join("langs", "en.json")), encoding="utf-8"
) as f:
en_data = json.load(f)
deepcopy_dict_missing_entries(
self.text_content, en_data.get("content", {})
)
except FileNotFoundError:
# Fallback or error handling if a language file is missing
# For now, text_content might remain empty or partially filled
# Consider loading English as a default more robustly here
if self.lang != "en": # Attempt to load English if primary lang failed
try:
with open(
resource_path(path.join("langs", "en.json")), encoding="utf-8"
) as f:
en_data = json.load(f)
self.text_content = en_data.get("content", {})
except Exception:
# If English also fails, text_content remains empty; app might have issues
pass # Or raise an error / show messagebox
except Exception:
# Handle other potential errors during language loading
pass
def systemTray(self):
"""Just to show little icon on system tray"""
try:
image = Image.open(resource_path(path.join("assets", "logo.ico")))
menu = (
MenuItem("Show", action=self.deiconify, default=True),
MenuItem(
"Quit", action=self.quit_software_from_tray
), # Added quit option
)
self.icon = Icon("PyMacroRecord", image, "PyMacroRecord", menu)
self.icon.run()
except Exception:
# Handle cases where icon cannot be created or run (e.g., headless environment)
pass
def quit_software_from_tray(self):
"""Safely quits the application from the system tray menu."""
if hasattr(self, "icon") and self.icon:
self.icon.stop()
self.quit_software(
force=True
) # Force quit to bypass save dialog if quitting from tray
def validate_input(self, action, value_if_allowed):
"""Prevents from adding letters on an Entry label"""
if action == "1": # Insert
try:
float(value_if_allowed)
return True
except ValueError:
return False
return True
def quit_software(self, force=False):
if not force and not self.macro_saved and self.macro_recorded:
wantToSave = confirm_save(self) # confirm_save uses self.text_content
if wantToSave:
RecordFileManagement(self, self.menu).save_macro()
elif wantToSave is None: # User cancelled the save dialog
return
if platform.lower() != "darwin":
if hasattr(self, "icon") and self.icon:
self.icon.stop()
# self.destroy() # This can cause issues if quit() is also called.
# Tk.quit() is generally preferred for stopping the mainloop and allowing cleanup.
self.quit()
# sys.exit() # Can be used if immediate exit is needed after cleanup
No I don't want a contribution fully did by AI, I'll probably do it soon by myself as I got time.