crontab と pactl を使用してシステムボリュームを変更する

crontab と pactl を使用してシステムボリュームを変更する

crontab を使用して Ubuntu 14.04 システム ボリュームを変更しようとしています。次のコマンドを使用しています:

pactl set-sink-volume 1 75%

ターミナルで使用したり、このコマンドを含むスクリプトを実行したりすると正常に動作しますが、システムが crontab からこのコードを実行したり、crontab で実行されるスクリプトを介して実行したりすると、システムは音量を変更しません。どうすればこれを修正できますか?

私も試してみました

amixer -D pulse sset Master 75%

Crontabは次のようになります(テスト目的で1分ごとに)

* * * * * pactl set-sink-volume 1 75%

または

* * * * * /usr/bin/pactl set-sink-volume 1 75\%

答え1

理解するのに少し時間がかかりましたが、次のようにすることができます:

1 9 * * * export DISPLAY=:0 && amixer -D pulse sset Master 48000

1 22 * * * export DISPLAY=:0 && amixer -D pulse sset Master 32000

100% の音量はおよそ 64000 です。これにより、音量は 9 で上がり、22 で下がります。また、sudo ではなく、ユーザーの crontab で行われます。

答え2

で始まりますウブントゥ 19.10、単純なDISPLAY=:0(または 0.0 でも)cron では役に立たず、次のエラーが発生しました:

Connection failure: Connection refused
pa_context_connect() failed: Connection refused

代わりに次のことをしなければなりませんでした(これは私の夜は無音cron ですが、必要に応じて調整できます):

30 21 * * * bash -l -c "DISPLAY=:0.0 pactl --server unix:/run/user/$(id -u)/pulse/native set-sink-mute @DEFAULT_SINK@ on"

壊す:

  • bash -lログインシェル用。
  • DISPLAY=:0.0展示用
  • --server unix:/run/user/$(id -u)/pulse/native: (cron はログイン シェル内にないため、明示的なファイル記述子)
  • @DEFAULT_SINK@ or 0: デフォルトまたは最初のオーディオデバイス。特定のデバイスの場合は、完全なデバイス名を使用できます。例:alsa_output.pci-0000_00_1f.3.analog-stereo
  • on(またはoff):ミュートのオン/オフ

Ubuntu 19.10 で追加する必要があったのは--serverarg でした。

答え3

cronからのコマンドの実行

多くの場合、問題なく動作し、信頼性も高いのですが、例えばGUIアプリケーションを実行する必要がある場合や、環境変数が関係するその他の状況では、cronジョブ(の組み合わせ)を適切に設定する方法を見つけるのはかなり大変です。どれの環境変数を設定する必要があるかどうか、またその方法を説明します。

代替

このような状況では、現在のユーザーの環境から、特定の時間にコマンドを実行したり、完全な「デイ プログラム」を実行したりするための簡単な代替手段があると便利です。

それが以下のスクリプトが行うことです。次のような単純な形式でテキスト ファイルにリストされているコマンドやアプリケーションを実行します。

11:09,gedit
11:10,gnome-terminal
11:20,pactl set-sink-volume 1 10%

あなたのコマンドでテストしましたが、問題なく動作しました。

セットアップ方法

セットアップは 3 つの小さなファイルで構成されており、同じフォルダーに保存する必要があります。これらのファイルの 1 つ ( command_data.txt) に、コマンドと、コマンドを実行する時刻をリストする必要があります。

次の形式を使用します。

time/comma/command (no spaces around the comma)

たとえば、5 分で音量を 100% まで上げるには、次のようにします。

11:20,pactl set-sink-volume 1 0%
11:21,pactl set-sink-volume 1 20%
11:22,pactl set-sink-volume 1 40%
11:23,pactl set-sink-volume 1 60%
11:24,pactl set-sink-volume 1 80%
11:25,pactl set-sink-volume 1 100%

ファイル:

前述のとおり、3 つのファイルは 1 つの同じフォルダーに配置する必要があります。

ファイル 1、メイン スクリプト。 それを空のファイルにコピーし、名前を付けて保存しschedule.py(名前はそのままに)、実行可能にします(重要)

#!/usr/bin/env python3

import subprocess
import time
import datetime
import os

cmd_data = os.path.dirname(os.path.abspath(__file__))+"/command_data.txt"

with open(cmd_data) as data:
    s = [item.strip().split(",")+[None] for item in data.readlines()]

def currtime(set_time):
    return int(set_time.split(":")[0])*60+int(set_time.split(":")[1])

def run_command(t, now, cmd, last_run):
    if currtime(t) == now and last_run != int(time.strftime("%d%m%Y"))+int(now):
        subprocess.Popen(["/bin/bash", "-c", cmd])
    else:
      pass

while True:
    now = currtime(str(datetime.datetime.now().time())[:5])
    for i in range(len(s)):
        cmdata = s[i]       
        run_command(cmdata[0], now, cmdata[1], cmdata[2])
        s[i][2] = int(time.strftime("%d%m%Y"))+int(now)
    time.sleep(30)

ファイル 2、スケジュールを開始/停止するスクリプト。 名前を付けて保存しrun_schedule.py(名前はそのまま)、実行可能にします(重要)

#!/usr/bin/env python3

import os
import subprocess

script_dir = os.path.dirname(os.path.abspath(__file__))
cmd = "ps -ef | grep schedule.py"
run = subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8").split("\n")
match = [line for line in run if script_dir+"/"+"schedule.py" in line]

if len(match) != 0:
    subprocess.Popen(["kill", match[0].split()[1]])
    subprocess.Popen(["notify-send", "Schedule stopped..."])
else:
    subprocess.Popen(["/bin/bash", "-c", script_dir+"/"+"schedule.py"])
    subprocess.Popen(["notify-send", "Schedule runs..."])

ファイル3、空のファイルを作成し、名前をcommand_data.txt 「設定方法」で説明されているように、コマンドを入力してください。

次のコマンドでスケジュールを開始/停止(切り替え)します。

/path/to/run_schedule.py

通知メッセージが表示されます:

ここに画像の説明を入力してください または:ここに画像の説明を入力してください

説明

ファイルの機能:

スクリプトschedule.pyが開始されると、 からコマンドとそのスケジュールされた実行時間を読み取りますcommand_data.txt。ループでは、現在の時刻が、リストされたコマンドのスケジュールされた時刻と比較されます。現在の時刻が、スケジュールされたジョブ時刻の 1 つ以上に等しい場合、コマンドが実行され、現在の時刻に対して「完了」としてマークされます。

スクリプトはrun_schedule.pyメイン スクリプト ( schedule.py) が実行中かどうかを確認します。実行中の場合はジョブが強制終了され、実行中でない場合はスクリプトが開始されます。どちらの場合も、確認通知が表示されます。

答え4

ありがとうございます。PIR がトリガーされると、モーション スクリーン/オーディオが機能するようになりました。

import time
import subprocess
from gpiozero import MotionSensor
import os
import logging
import time
import subprocess

os.environ["DISPLAY"] = ":0"

# Set up logging
logging.basicConfig(filename='output.log', level=logging.DEBUG,
                    format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')


# Read in value of default_sink.txt for default_sink
with open('default_sink.txt', 'r') as f:
    default_sink = f.read().strip()

print(default_sink)

# Constants
IR_SENSOR_PIN = 4  # GPIO pin number of IR sensor
TIMER_DURATION = 60  # Timer duration in seconds

# Initialize IR sensor and timer
ir_sensor = MotionSensor(IR_SENSOR_PIN)
timer = time.time() + TIMER_DURATION  # Set timer to expire immediately

#run pactl command like this DISPLAY=:0.0 pactl --server unix:/run/user/1000/pulse/native set-sink-mute 0 on"

while True:
    if ir_sensor.motion_detected:
        logging.debug("Motion detected")
        # Reset timer
        timer = time.time() + TIMER_DURATION
        # Turn on display and audio
        subprocess.call("xset dpms force on", shell=True)
        result = subprocess.run(f"DISPLAY=:0.0 pactl --server unix:/run/user/1000/pulse/native set-sink-mute '{default_sink}' 0", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        with open("output.log", "a") as f:
            f.write(result.stdout.decode())
            f.write(result.stderr.decode())
            f.write(f"Return code: {result.returncode}\n")
    elif timer and time.time() > timer:
        logging.debug("Timer expired")
        # Turn off display and audio
        subprocess.call("xset dpms force off", shell=True)
        result = subprocess.run(f"DISPLAY=:0.0 pactl --server unix:/run/user/1000/pulse/native set-sink-mute '{default_sink}' 1", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        with open("output.log", "a") as f:
            f.write(result.stdout.decode())
            f.write(result.stderr.decode())
            f.write(f"Return code: {result.returncode}\n")
        timer = time.time() + TIMER_DURATION  # Set timer to expire immediately
    time.sleep(1)

関連情報