diff --git a/TODO.md b/TODO.md
index 2e1982ea..f52f06e5 100644
--- a/TODO.md
+++ b/TODO.md
@@ -14,3 +14,5 @@ Code TODO
=========
1. Example plugin based off videos plugin
1. Check that videos can be removed or renamed
+ 1. Plugin has support for systemd so it can start it's own service
+ 1. Plugin has support for cronjobs
diff --git a/requirements.txt b/requirements.txt
index 52a84dda..8a416403 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,5 +6,4 @@ Jinja2>=2.10.1
mock>=3.0.5
psycopg2-binary>=2.8.3
pyyaml>=5.1.2
-youtube-dl>=2019.8.13
pytest>=5.2.1
\ No newline at end of file
diff --git a/test/example.en.vtt b/test/example.en.vtt
new file mode 100644
index 00000000..fb0fb9b2
--- /dev/null
+++ b/test/example.en.vtt
@@ -0,0 +1,48 @@
+WEBVTT
+Kind: captions
+Language: en
+
+00:00:00.000 --> 00:00:05.269 align:start position:0%
+
+okay<00:00:01.310> welcome<00:00:02.310> to<00:00:02.429> this<00:00:02.550> session<00:00:04.130> this<00:00:05.130> is
+
+00:00:05.269 --> 00:00:05.279 align:start position:0%
+okay welcome to this session this is
+
+
+00:00:05.279 --> 00:00:07.760 align:start position:0%
+okay welcome to this session this is
+called<00:00:05.879> the<00:00:05.970> kinetic<00:00:06.420> bunny<00:00:06.690> need<00:00:07.319> to<00:00:07.529> meet
+
+00:00:07.760 --> 00:00:07.770 align:start position:0%
+called the kinetic bunny need to meet
+
+
+00:00:07.770 --> 00:00:11.450 align:start position:0%
+called the kinetic bunny need to meet
+the<00:00:07.890> virtual<00:00:08.280> bonnie<00:00:08.519> emilio<00:00:09.380> stop<00:00:10.380> this<00:00:11.340> is
+
+00:00:11.450 --> 00:00:11.460 align:start position:0%
+the virtual bonnie emilio stop this is
+
+
+00:00:11.460 --> 00:00:15.410 align:start position:0%
+the virtual bonnie emilio stop this is
+jana<00:00:11.639> car<00:00:11.880> who<00:00:13.700> just<00:00:14.700> to<00:00:14.759> give<00:00:14.940> a<00:00:14.969> little<00:00:15.030> bit
+
+00:00:15.410 --> 00:00:15.420 align:start position:0%
+jana car who just to give a little bit
+
+
+00:00:15.420 --> 00:00:18.859 align:start position:0%
+jana car who just to give a little bit
+of<00:00:15.529> background<00:00:16.529> information<00:00:17.390> we<00:00:18.390> come<00:00:18.630> from<00:00:18.750> a
+
+00:00:18.859 --> 00:00:18.869 align:start position:0%
+of background information we come from a
+
+
+00:00:18.869 --> 00:00:21.109 align:start position:0%
+of background information we come from a
+company<00:00:19.109> called<00:00:19.199> le<00:00:19.529> code<00:00:19.740> from<00:00:20.550> finland<00:00:20.939> and
+
diff --git a/wrolpi/plugins/example_plugin/requirements.txt b/wrolpi/plugins/example_plugin/requirements.txt
new file mode 100644
index 00000000..359beeff
--- /dev/null
+++ b/wrolpi/plugins/example_plugin/requirements.txt
@@ -0,0 +1 @@
+# Add your pip requirements here
\ No newline at end of file
diff --git a/wrolpi/plugins/videos/api.py b/wrolpi/plugins/videos/api.py
index 7404c15c..04c451ce 100644
--- a/wrolpi/plugins/videos/api.py
+++ b/wrolpi/plugins/videos/api.py
@@ -71,6 +71,7 @@ def json_statuses_streamer(func):
'{"success": "stream-complete"}\n'
"""
+ @wraps(func)
def wrap(*a, **kw):
yield from (json.dumps({'status': i}) + '\n' for i in func(*a, **kw))
yield json.dumps({'success': 'stream-complete'}) + '\n'
diff --git a/wrolpi/plugins/videos/captions.py b/wrolpi/plugins/videos/captions.py
new file mode 100755
index 00000000..393db240
--- /dev/null
+++ b/wrolpi/plugins/videos/captions.py
@@ -0,0 +1,20 @@
+#! /usr/bin/env python3
+from typing import Generator
+
+import webvtt
+
+
+def get_caption_text(vtt_path: str) -> Generator:
+ for caption in webvtt.read(vtt_path):
+ text = str(caption.text).strip()
+ yield text
+
+
+def get_unique_caption_lines(vtt_path: str) -> Generator:
+ last_line = None
+ for text in get_caption_text(vtt_path):
+ for line in text.split('\n'):
+ if line and line != last_line:
+ last_line = line
+ yield line
+
diff --git a/wrolpi/plugins/videos/requirements.txt b/wrolpi/plugins/videos/requirements.txt
new file mode 100644
index 00000000..33f8ee2c
--- /dev/null
+++ b/wrolpi/plugins/videos/requirements.txt
@@ -0,0 +1,2 @@
+youtube-dl>=2019.8.13
+webvtt-py==0.4.2
\ No newline at end of file
diff --git a/wrolpi/plugins/videos/test/test_captions.py b/wrolpi/plugins/videos/test/test_captions.py
new file mode 100644
index 00000000..455ee3ee
--- /dev/null
+++ b/wrolpi/plugins/videos/test/test_captions.py
@@ -0,0 +1,30 @@
+import unittest
+
+from wrolpi.plugins.videos import captions
+
+
+class TestCaption(unittest.TestCase):
+
+ def test_example_en_vtt(self):
+ vtt_path = 'test/example.en.vtt'
+
+ expected = ['okay welcome to this session this is',
+ 'okay welcome to this session this is',
+ 'okay welcome to this session this is\ncalled the kinetic bunny need to meet',
+ 'called the kinetic bunny need to meet',
+ 'called the kinetic bunny need to meet\nthe virtual bonnie emilio stop this is',
+ 'the virtual bonnie emilio stop this is',
+ 'the virtual bonnie emilio stop this is\njana car who just to give a little bit',
+ 'jana car who just to give a little bit',
+ 'jana car who just to give a little bit\nof background information we come from a',
+ 'of background information we come from a',
+ 'of background information we come from a\ncompany called le code from finland and']
+ self.assertEqual(list(captions.get_caption_text(vtt_path)), expected)
+
+ expected = ['okay welcome to this session this is',
+ 'called the kinetic bunny need to meet',
+ 'the virtual bonnie emilio stop this is',
+ 'jana car who just to give a little bit',
+ 'of background information we come from a',
+ 'company called le code from finland and']
+ self.assertEqual(list(captions.get_unique_caption_lines(vtt_path)), expected)
diff --git a/wrolpi/web.py b/wrolpi/web.py
index a0189400..11debb98 100644
--- a/wrolpi/web.py
+++ b/wrolpi/web.py
@@ -21,7 +21,7 @@ ROOT_CONFIG = {
class ClientRoot(object):
def __init__(self):
- # Install plugins defined in plugins.plugins
+ # Install plugins defined in user_plugins
for name, plugin in PLUGINS.items():
setattr(self, name, plugin.ClientRoot())