Advertisement
JohnSmithSnippets
Oct 29th, 2024
20
0
Never
This is comment for paste Untitled
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env python3
  2. # Original: <https://pastebin.com/xsUhLTtH>
  3. """
  4. Moves a recording file when filename matches match_keyword X to output_folder Y
  5.  
  6. Created by `Sateviss` :chatting:
  7. Refactored and cleaned by `JohnSmith`
  8. """
  9.  
  10. import logging
  11. from pathlib import Path
  12. from time import sleep
  13. from typing import NamedTuple
  14.  
  15. import obspython as obs
  16.  
  17. # Configuration constants
  18. NUM_OF_MATCHES = 4
  19. """Number of match strings to be configurable in OBS"""
  20.  
  21. NUM_OF_POLL_TRIES = 15
  22. """Num of polls to try between OBS signal and moving the file."""
  23. POLL_WAIT_TIME = 1
  24. """Seconds to wait between each file poll."""
  25.  
  26. LOG_FORMAT = "%(asctime)s | %(name)s | %(levelname)s | %(message)s"
  27. """Logging format for the script."""
  28. LOG_LEVEL = logging.INFO
  29. """Logging level for stdout."""
  30. LOG_FILEPATH = Path.home() / "obs_recording_migration_script.log"
  31. """Logging file path for the script."""
  32.  
  33.  
  34. # Data classes
  35. class RecordingMatchCase(NamedTuple):
  36.     """Entry type to encode a match case for recording migration."""
  37.  
  38.     match_keyword: str
  39.     """Keyword to match against the recording filename."""
  40.  
  41.     output_folder: Path
  42.     """Output folder to move the recording file to."""
  43.  
  44.  
  45. # Initialisation functions
  46. def init_logger() -> logging.Logger:
  47.     """Initialises a logger object for the script."""
  48.  
  49.     print("Initialising logger object...")
  50.  
  51.     # Setup logging to stdout
  52.     logging.basicConfig(level=LOG_LEVEL, format=LOG_FORMAT)
  53.     logger = logging.getLogger(__name__)
  54.  
  55.     # Setup logging to logfile
  56.     file_handler = logging.FileHandler(LOG_FILEPATH)
  57.     file_handler.setFormatter(logging.Formatter(LOG_FORMAT))
  58.  
  59.     # Join logging handlers together
  60.     logger.addHandler(file_handler)
  61.  
  62.     logger.info(f"Logger initialized! {LOG_FILEPATH=}")
  63.     return logger
  64.  
  65.  
  66. # Global mutable variables
  67. script_logger = init_logger()
  68. """Logger object for the script."""
  69. recording_migrate_mappings: set[RecordingMatchCase] = set()
  70. """Mapping of match strings to output paths."""
  71.  
  72.  
  73. # ================================
  74. # OBS invocation hooks
  75. # ================================
  76. def script_description() -> str:
  77.     """Return the plugin description string."""
  78.     # Just re-using the script docstring; see top of file
  79.     return __doc__ if __doc__ is not None else ""
  80.  
  81.  
  82. def script_load(settings) -> None:
  83.     """Initialises hook for stop recording signal during plugin load."""
  84.  
  85.     script_logger.debug("Attaching handler...")
  86.  
  87.     try:
  88.         obs_frontend = obs.obs_frontend_get_recording_output()
  89.         signal_handler = obs.obs_output_get_signal_handler(obs_frontend)
  90.  
  91.         obs.signal_handler_connect(signal_handler, "stop", signal_handler_function)
  92.  
  93.     except Exception as err:
  94.         script_logger.error(f"Error occurred during handler attachment: {err}")
  95.         raise err
  96.  
  97.     script_logger.info("Script handler hook was successfully attached!")
  98.  
  99.  
  100. def script_properties():
  101.     """Hook to setup initial OBS properties for the script."""
  102.  
  103.     script_logger.info("Setting up OBS properties...")
  104.  
  105.     # global NUM_OF_MATCHES
  106.     props = obs.obs_properties_create()
  107.  
  108.     for i in range(NUM_OF_MATCHES):
  109.  
  110.         obs.obs_properties_add_text(
  111.             props, f"match{i}", f"Match {i + 1}", obs.OBS_TEXT_DEFAULT
  112.         )
  113.         obs.obs_properties_add_path(
  114.             props, f"path{i}", f"Path {i + 1}", obs.OBS_PATH_DIRECTORY, None, None
  115.         )
  116.  
  117.     return props
  118.  
  119.  
  120. def script_update(settings) -> None:
  121.     """Hook to update recording patterns"""
  122.  
  123.     # global NUM_OF_MATCHES
  124.     global recording_migrate_mappings
  125.  
  126.     for i in range(NUM_OF_MATCHES):
  127.  
  128.         new_match_entry = RecordingMatchCase(
  129.             match_keyword=obs.obs_data_get_string(settings, f"match{i}"),
  130.             output_folder=Path(obs.obs_data_get_string(settings, f"path{i}")),
  131.         )
  132.  
  133.         recording_migrate_mappings.add(new_match_entry)
  134.         script_logger.debug(f"Loaded matcher {i}:", new_match_entry)
  135.  
  136.  
  137. def signal_handler_function(calldata) -> None:
  138.     """Handles the recording stop signal and moves the file accordingly."""
  139.     # global recording_migrate_mappings
  140.  
  141.     script_logger.info("Handler triggered! Running recording migration hook...")
  142.  
  143.     try:
  144.  
  145.         # Get the last recording file path
  146.         orig_recording_raw: str = obs.obs_frontend_get_last_recording()
  147.         if not orig_recording_raw:
  148.             script_logger.error(
  149.                 f"No recording file path was returned by OBS: {orig_recording_raw=}",
  150.             )
  151.             return
  152.         orig_recording: Path = Path(orig_recording_raw)
  153.         script_logger.debug(f"Recording file path: {orig_recording=}")
  154.  
  155.         # Wait for the file to be fully available
  156.         for poll_i in range(NUM_OF_POLL_TRIES):
  157.             sleep(POLL_WAIT_TIME)
  158.             if orig_recording.exists():
  159.                 break
  160.             script_logger.debug(f"Recording file not found yet: {poll_i=}")
  161.         else:
  162.             script_logger.error(f"Recording file cannot be found: {orig_recording=}")
  163.             # TODO: turn these returns into error raises?
  164.             return
  165.  
  166.         script_logger.info(f"Valid initial recording file given! {orig_recording=}")
  167.         script_logger.debug("Trying to match file...")
  168.  
  169.         # Find matching file migration mapping
  170.         for mapping in recording_migrate_mappings:
  171.  
  172.             # Checking if the current match is valid...
  173.             if mapping.match_keyword.casefold() not in orig_recording.name.casefold():
  174.                 script_logger.debug(f"Not a match! {mapping=}")
  175.                 continue
  176.  
  177.             script_logger.info(f"Suitable match found: matcher={mapping}")
  178.  
  179.             # Checking if the matching output folder is currently valid
  180.             # NOTE: if this gets race conditioned somehow, just throw everything below into a retry loop
  181.             if not mapping.output_folder.exists():
  182.                 script_logger.error(
  183.                     f"Output path does not exist: {mapping.output_folder=}"
  184.                 )
  185.                 break
  186.  
  187.             if not mapping.output_folder.is_dir():
  188.                 script_logger.error(
  189.                     f"Output Path is not actually a directory: {mapping.output_folder=}"
  190.                 )
  191.                 break
  192.  
  193.             # Constructing the new recording path
  194.             new_recording_path = mapping.output_folder / orig_recording.name
  195.             script_logger.debug(
  196.                 f"Valid resulting recording path constructed: {new_recording_path}"
  197.             )
  198.  
  199.             script_logger.debug("Attempting file migration...")
  200.             orig_recording.rename(new_recording_path)
  201.             script_logger.info(
  202.                 f"Recording file has been migrated to: {new_recording_path}"
  203.             )
  204.             break
  205.  
  206.         else:
  207.             script_logger.error(
  208.                 f"No keyword match found for recording: {orig_recording}"
  209.             )
  210.  
  211.     except Exception as err:
  212.         script_logger.error(f"Error occurred during file migration hook: {err}")
  213.         # TODO: should error messages be chained up to OBS itself?
  214.         # raise err
  215.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement