summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSacha Chua <sacha@sachachua.com>2022-10-25 11:13:23 -0400
committerSacha Chua <sacha@sachachua.com>2022-10-25 11:13:23 -0400
commite42e761dca58557799084ceafdf2088d85fe40c5 (patch)
tree7a88d305b303b763b7ad8c849b0902b3068634bd
parent765036da2fc05fd45693bec60ce911b01636e9be (diff)
downloademacsconf-ansible-e42e761dca58557799084ceafdf2088d85fe40c5.tar.xz
emacsconf-ansible-e42e761dca58557799084ceafdf2088d85fe40c5.zip
Caption daemon
-rw-r--r--README.org2
-rw-r--r--group_vars/all.yml1
-rw-r--r--inventory.yml1
-rw-r--r--roles/caption/defaults/main.yml2
-rw-r--r--roles/caption/tasks/main.yml60
-rwxr-xr-xroles/caption/templates/captions.init.d78
-rwxr-xr-xroles/caption/templates/inotify-process-captions.sh15
-rwxr-xr-xroles/caption/templates/process-captions.py46
8 files changed, 174 insertions, 31 deletions
diff --git a/README.org b/README.org
index 9b90e7b..d5df01d 100644
--- a/README.org
+++ b/README.org
@@ -163,3 +163,5 @@ ansible-playbook -i inventory.yml prod-playbook.yml --tags test-stream-pattern -
Set up whisper
ansible-playbook -i inventory.yml prod-playbook.yml --tags caption
+
+ffmpeg -y -i handwritten/reencode.webm -t 60 -vcodec copy -acodec copy test.webm
diff --git a/group_vars/all.yml b/group_vars/all.yml
index bc8f39a..8f86f46 100644
--- a/group_vars/all.yml
+++ b/group_vars/all.yml
@@ -1,4 +1,5 @@
docker: false
+emacsconf_user: orga
emacsconf_tracks:
- name: General
id: gen
diff --git a/inventory.yml b/inventory.yml
index 6608342..997e01f 100644
--- a/inventory.yml
+++ b/inventory.yml
@@ -4,6 +4,7 @@ prod:
ansible_host: res.emacsconf.org
ansible_python_interpreter: /usr/bin/python3
ansible_become: true
+ emacsconf_group: org
front:
ansible_host: front0.emacsconf.org
remote_user: orga
diff --git a/roles/caption/defaults/main.yml b/roles/caption/defaults/main.yml
new file mode 100644
index 0000000..c158118
--- /dev/null
+++ b/roles/caption/defaults/main.yml
@@ -0,0 +1,2 @@
+emacsconf_caption_dir: /data/emacsconf/{{ emacsconf_year }}
+
diff --git a/roles/caption/tasks/main.yml b/roles/caption/tasks/main.yml
index 6396339..7fe1570 100644
--- a/roles/caption/tasks/main.yml
+++ b/roles/caption/tasks/main.yml
@@ -5,10 +5,11 @@
- ffmpeg
- cmake
- jq
+ - inotify-tools
- name: Install whisper
ansible.builtin.pip:
name: git+https://github.com/openai/whisper.git
-- name: Install lhotse
+- name: Install Python packages
ansible.builtin.pip:
name:
- lhotse
@@ -16,18 +17,57 @@
- tqdm
- torchaudio
- num2words
-- name: Copy the shell script
- tags: process-captions
+- name: Create group
+ group:
+ name: "{{ emacsconf_group }}"
+ state: present
+- name: Create user
+ user:
+ name: "{{ emacsconf_user }}"
+ group: "{{ emacsconf_group }}"
+ state: present
+- name: Ensure the directory exists
+ file:
+ path: "{{ emacsconf_caption_dir }}"
+ state: directory
+- name: Copy the script for processing the files
+ tags: process-captions, wip
template:
src: process-captions.py
- dest: /data/emacsconf/{{ emacsconf_year }}/process-captions.py
- mode: 0755
- owner: sachac
- group: org
+ dest: "{{ emacsconf_caption_dir }}/process-captions.py"
+ mode: 0775
+- name: Copy the inotify script
+ tags: process-captions
+ template:
+ src: inotify-process-captions.sh
+ dest: "{{ emacsconf_caption_dir }}/inotify-process-captions.sh"
+ mode: 0775
- name: Copy talks.json
tags: talks-json
template:
src: talks.json
- dest: /data/emacsconf/{{ emacsconf_year }}/talks.json
- owner: sachac
- group: org
+ dest: "{{ emacsconf_caption_dir }}/talks.json"
+ mode: 0664
+- name: Install init.d configuration
+ tags: system
+ become: true
+ template:
+ src: captions.init.d
+ dest: /etc/init.d/captions
+ owner: root
+ group: root
+ mode: 0755
+- name: Change the group for all the files
+ tags: wip
+ file:
+ dest: "{{ emacsconf_caption_dir }}"
+ group: "{{ emacsconf_group }}"
+ mode: "g+rwX"
+ recurse: true
+- name: Restart caption monitoring service
+ become: true
+ tags: wip
+ service:
+ name: captions
+ enabled: true
+ state: started
diff --git a/roles/caption/templates/captions.init.d b/roles/caption/templates/captions.init.d
new file mode 100755
index 0000000..5e1c9f2
--- /dev/null
+++ b/roles/caption/templates/captions.init.d
@@ -0,0 +1,78 @@
+#!/bin/sh
+# {{ ansible_managed }}
+
+### BEGIN INIT INFO
+# Provides: captions
+# Required-Start: $local_fs $remote_fs $network $syslog
+# Required-Stop: $local_fs $remote_fs $network $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: starts captioning monitor
+# Description: starts captioning monitor using start-stop-daemon
+### END INIT INFO
+
+PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
+USER="{{ emacsconf_user }}"
+GROUP="{{ emacsconf_group }}"
+DESC="EmacsConf captions"
+NAME="captions"
+WORKDIR="{{ emacsconf_caption_dir }}"
+
+set -e
+
+. /lib/lsb/init-functions
+
+start() {
+ echo "Starting $DESC... "
+
+ start-stop-daemon --start --chuid "$USER:$GROUP" -d $WORKDIR --background --make-pidfile --pidfile /var/run/$NAME.pid --exec "$WORKDIR/inotify-process-captions.sh > /var/log/$NAME.log" || true
+ echo "done"
+}
+
+#We need this function to ensure the whole process tree will be killed
+killtree() {
+ local _pid=$1
+ local _sig=${2-TERM}
+ for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
+ killtree ${_child} ${_sig}
+ done
+ kill -${_sig} ${_pid}
+}
+
+stop() {
+ echo "Stopping $DESC... "
+ if test -f /var/run/$NAME.pid; then
+ while test -d /proc/$(cat /var/run/$NAME.pid); do
+ killtree $(cat /var/run/$NAME.pid) 15
+ sleep 0.5
+ done
+ rm /var/run/$NAME.pid
+ fi
+ echo "done"
+}
+
+status() {
+ status_of_proc -p /var/run/$NAME.pid "" "$NAME" && exit 0 || exit $?
+}
+
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ restart)
+ stop
+ start
+ ;;
+ status)
+ status
+ ;;
+ *)
+ echo "Usage: $NAME {start|stop|restart|status}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/roles/caption/templates/inotify-process-captions.sh b/roles/caption/templates/inotify-process-captions.sh
new file mode 100755
index 0000000..ce5f416
--- /dev/null
+++ b/roles/caption/templates/inotify-process-captions.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+# {{ ansible_managed }}
+
+# This script waits for new webm and mov files
+# in the current directory (recursively) and then calls
+# process-captions to process all the new files that need work.
+inotifywait -r -m "{{ emacsconf_caption_dir }}" -e create -e moved_to |
+ while read directory action file; do
+ if [[ "$file" =~ .*(webm|mov)$ ]]; then
+ "{{ emacsconf_caption_dir }}"/process-captions.py | tee -a "{{ emacsconf_caption_dir }}/captions.log"
+ elif [[ "$file" =~ .*(vtt|srv2|ogg|opus)$ ]]; then
+ # Copy to backstage area
+ rsync --ignore-existing $directory/$file orga@media.emacsconf.org:/var/www/media.emacsconf.org/{{ emacsconf_year }}/backstage/
+ fi
+ done
diff --git a/roles/caption/templates/process-captions.py b/roles/caption/templates/process-captions.py
index 6ad890a..72e9ad2 100755
--- a/roles/caption/templates/process-captions.py
+++ b/roles/caption/templates/process-captions.py
@@ -46,7 +46,8 @@ AUDIO_REGEXP = '\.(ogg|opus)$'
ALWAYS = False
TRIM_AUDIO = False
MODEL = os.environ.get('MODEL', 'large') # Set to tiny for testing
-JSON_FILE = '/data/emacsconf/2022/talks.json'
+WORK_DIR = "{{ emacsconf_caption_dir }}"
+JSON_FILE = os.path.join(WORK_DIR, 'talks.json')
def get_slug_from_filename(filename):
m = re.search('emacsconf-[0-9]+-([a-z]+)--', filename)
@@ -100,14 +101,14 @@ def get_files_to_work_on(directory):
return needs_work
def extract_audio(work):
- output = subprocess.check_output(['ffprobe', video_file], stderr=subprocess.STDOUT)
+ output = subprocess.check_output(['ffprobe', work['video']], stderr=subprocess.STDOUT)
extension = 'opus'
if 'Audio: vorbis' in output.decode():
extension = 'ogg'
- new_file = os.path.join(os.path.dirname(video_file), base_name(video_file) + '.' + extension)
- acodec = 'copy' if re.search('webm$', video_file) else 'libopus'
- log("Extracting audio from %s acodec %s" % (video_file, acodec))
- output = subprocess.check_output(['ffmpeg', '-y', '-i', video_file, '-acodec', acodec, '-vn', new_file], stderr=subprocess.STDOUT)
+ new_file = work['base'] + '.' + extension
+ acodec = 'copy' if re.search('webm$', work['video']) else 'libopus'
+ log("Extracting audio from %s acodec %s" % (work['video'], acodec))
+ output = subprocess.check_output(['ffmpeg', '-y', '-i', work['video'], '-acodec', acodec, '-vn', new_file], stderr=subprocess.STDOUT)
work['audio'] = new_file
return work
@@ -213,19 +214,22 @@ def base_name(s):
# assert(base_name('/home/sachac/current/sqlite/emacsconf-2022-sqlite--using-sqlite-as-a-data-source-a-framework-and-an-example--andrew-hyatt--normalized.webm.vtt') == 'emacsconf-2022-sqlite--using-sqlite-as-a-data-source-a-framework-and-an-example--andrew-hyatt--main')
log(f"MODEL {MODEL} ALWAYS {ALWAYS} TRIM_AUDIO {TRIM_AUDIO}")
-directory = sys.argv[1] if len(sys.argv) > 1 else "~/current"
+directory = sys.argv[1] if len(sys.argv) > 1 else WORK_DIR
+
needs_work = get_files_to_work_on(directory)
-if THREADS > 0:
- torch.set_num_threads(THREADS)
-for work in needs_work:
- log("Started processing %s" % work['base'])
- if work['audio']:
- if ALWAYS or not 'vtt' in work:
- work = generate_captions(work)
- if ALWAYS or not 'srv2' in work:
- work = generate_srv2(work)
-# print("Aligning words", audio_file, datetime.datetime.now())
-# word_cuts = align_words(cuts)
-# convert_cuts_to_word_timing(audio_file, word_cuts)
- log("Done %s" % str(work['base']))
-
+if len(needs_work) > 0:
+ if THREADS > 0:
+ torch.set_num_threads(THREADS)
+ for work in needs_work:
+ log("Started processing %s" % work['base'])
+ if work['audio']:
+ if ALWAYS or not 'vtt' in work:
+ work = generate_captions(work)
+ if ALWAYS or not 'srv2' in work:
+ work = generate_srv2(work)
+ # print("Aligning words", audio_file, datetime.datetime.now())
+ # word_cuts = align_words(cuts)
+ # convert_cuts_to_word_timing(audio_file, word_cuts)
+ log("Done %s" % str(work['base']))
+else:
+ log("No work needed.")