audiogen_script.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. """
  2. script audiogenerator: uses an external program to generate audio URIs
  3. a script can be any valid executable in
  4. $XDG_CONFIG_DIR/larigira/scripts/<name>; for security reasons, it must be
  5. executable and owned by the current user. The audiospec can specify arguments
  6. to the script, while the environment cannot be customized (again, this is for
  7. security reasons).
  8. The script should assume a minimal environment, and being run from /. It must
  9. output one URI per line; please remember that URI must be understood from mpd,
  10. so file paths are not valid; file:///file/path.ogg is a valid URI instead.
  11. The output MUST be UTF-8-encoded.
  12. Empty lines will be skipped. stderr will be logged, so please be careful. any
  13. non-zero exit code will result in no files being added.and an exception being
  14. logged.
  15. """
  16. import logging
  17. import os
  18. import subprocess
  19. from .config import get_conf
  20. log = logging.getLogger(__name__)
  21. def generate(spec):
  22. """
  23. Recognized arguments (fields in spec):
  24. - name [mandatory] script name
  25. - args [default=empty] arguments, colon-separated
  26. """
  27. conf = get_conf()
  28. spec.setdefault("args", "")
  29. if type(spec["args"]) is str:
  30. args = spec["args"].split(";")
  31. args = list(spec["args"])
  32. for attr in ("name",):
  33. if attr not in spec:
  34. raise ValueError("Malformed audiospec: missing '%s'" % attr)
  35. if "/" in spec["name"]:
  36. raise ValueError(
  37. "Script name is a filename, not a path ({} provided)".format(spec["name"])
  38. )
  39. scriptpath = os.path.join(conf["SCRIPTS_PATH"], spec["name"])
  40. if not os.path.exists(scriptpath):
  41. raise ValueError("Script %s not found", spec["name"])
  42. if not os.access(scriptpath, os.R_OK | os.X_OK):
  43. raise ValueError("Insufficient privileges for script %s" % scriptpath)
  44. if os.stat(scriptpath).st_uid != os.getuid():
  45. raise ValueError(
  46. "Script %s owned by %d, should be owned by %d"
  47. % (spec["name"], os.stat(scriptpath).st_uid, os.getuid())
  48. )
  49. try:
  50. log.info("Going to run %s", [scriptpath] + args)
  51. env = dict(
  52. HOME=os.environ["HOME"],
  53. PATH=os.environ["PATH"],
  54. MPD_HOST=conf["MPD_HOST"],
  55. MPD_PORT=str(conf["MPD_PORT"]),
  56. )
  57. if "TMPDIR" in os.environ:
  58. env["TMPDIR"] = os.environ["TMPDIR"]
  59. out = subprocess.check_output([scriptpath] + args, env=env, cwd="/")
  60. except subprocess.CalledProcessError as exc:
  61. log.error("Error %d when running script %s", exc.returncode, spec["name"])
  62. return []
  63. out = out.decode("utf-8")
  64. out = [p for p in out.split("\n") if p]
  65. logging.debug("Script %s produced %d files", spec["name"], len(out))
  66. return out
  67. generate.description = "Generate audio through an external script. " "Experts only."