From d8ca4182388a6fe22d3f0f35c6967dd5db2fca8d Mon Sep 17 00:00:00 2001 From: Luc Sinet Date: Wed, 31 Jul 2019 17:33:24 +0200 Subject: [PATCH 1/8] develop #comment Rename video_controller into controller. --- OpenCast/{video_controller.py => controller.py} | 6 +++--- OpenCast/server.py | 4 ++-- ...video_controller_test.py => controller_test.py} | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) rename OpenCast/{video_controller.py => controller.py} (96%) rename test/{video_controller_test.py => controller_test.py} (88%) diff --git a/OpenCast/video_controller.py b/OpenCast/controller.py similarity index 96% rename from OpenCast/video_controller.py rename to OpenCast/controller.py index 629b3253..fed3b328 100644 --- a/OpenCast/video_controller.py +++ b/OpenCast/controller.py @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) -class VideoController(object): +class Controller(object): def __init__(self, player, downloader): self._player = player self._downloader = downloader @@ -99,8 +99,8 @@ def _queue_video(self, video, dl_callback, first): self._downloader.queue([video], dl_callback, first) -def make_video_controller(): +def make_controller(): player = player_wrapper.make_wrapper() downloader = video_downloader.make_video_downloader() - return VideoController(player, downloader) + return Controller(player, downloader) diff --git a/OpenCast/server.py b/OpenCast/server.py index 8601dd33..5ccfb6ec 100755 --- a/OpenCast/server.py +++ b/OpenCast/server.py @@ -14,7 +14,7 @@ from . import ( config, - video_controller, + controller, ) logger = logging.getLogger(__name__) @@ -24,7 +24,7 @@ class Server(object): def __init__(self): self._app = Bottle() - self._controller = video_controller.make_video_controller() + self._controller = controller.make_controller() TEMPLATE_PATH.insert(0, os.path.join(app_path, 'views')) SimpleTemplate.defaults['get_url'] = self._app.get_url diff --git a/test/video_controller_test.py b/test/controller_test.py similarity index 88% rename from test/video_controller_test.py rename to test/controller_test.py index 0451c1ea..3f519084 100644 --- a/test/video_controller_test.py +++ b/test/controller_test.py @@ -3,18 +3,18 @@ Mock, patch, ) +from OpenCast.controller import Controller from OpenCast.video import Video -from OpenCast.video_controller import VideoController from .util import TestCase -class VideoControllerTest(TestCase): +class ControllerTest(TestCase): @classmethod def setUpClass(cls): cls.player = Mock() cls.downloader = Mock() - cls.controller = VideoController(cls.player, cls.downloader) + cls.controller = Controller(cls.player, cls.downloader) def tearDown(self): self.player.reset_mock() @@ -25,14 +25,14 @@ def test_stream_video_simple_url(self): self.downloader.queue.assert_called_once_with( [Video('url')], ANY, True) - @patch('OpenCast.video_controller.Video') + @patch('OpenCast.controller.Video') def test_stream_video_local_path(self, video_mock): video_mock.is_local.return_value = True self.controller.stream_video('url') self.player.play.assert_called_once_with(ANY) - @patch('OpenCast.video_controller.uuid') + @patch('OpenCast.controller.uuid') def test_stream_video_playlist(self, uuid_mock): uuid_mock.uuid4.return_value = 'id' self.downloader.extract_playlist.return_value = ['url1', 'url2'] @@ -48,14 +48,14 @@ def test_queue_video_simple_url(self): self.downloader.queue.assert_called_once_with( [Video('url')], ANY, False) - @patch('OpenCast.video_controller.Video') + @patch('OpenCast.controller.Video') def test_queue_video_local_path(self, video_mock): video_mock.is_local.return_value = True self.controller.queue_video('url') self.player.queue.assert_called_once_with(ANY) - @patch('OpenCast.video_controller.uuid') + @patch('OpenCast.controller.uuid') def test_queue_video_playlist(self, uuid_mock): uuid_mock.uuid4.return_value = 'id' self.downloader.extract_playlist.return_value = ['url1', 'url2'] From e2a2caac19a10823b6877f0ac88e0b1be6ea0218 Mon Sep 17 00:00:00 2001 From: Luc Sinet Date: Wed, 31 Jul 2019 17:53:37 +0200 Subject: [PATCH 2/8] develop #comment Update dependencies. --- Pipfile.lock | 63 ++++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 01d57950..d154c1f6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -18,11 +18,11 @@ "default": { "bottle": { "hashes": [ - "sha256:9c310da61e7df2b6ac257d8a90811899ccb3a9743e77e947101072a2e3186726", - "sha256:ca43beafbdccabbe31b758a4f34d1e44985a9b9539516775208b2b0f903eafa0" + "sha256:1896a33b2c7c5be07491e6789e341f2e9593a0ff024cc0374615118587c81647", + "sha256:e9eaa412a60cc3d42ceb42f58d15864d9ed1b92e9d630b8130c871c5bb16107c" ], "index": "pypi", - "version": "==0.12.16" + "version": "==0.12.17" }, "dbus-python": { "hashes": [ @@ -68,42 +68,43 @@ }, "pathlib2": { "hashes": [ - "sha256:25199318e8cc3c25dcb45cbe084cc061051336d5a9ea2a12448d3d8cb748f742", - "sha256:5887121d7f7df3603bca2f710e7219f3eca0eb69e0b7cc6e0a022e155ac931a7" + "sha256:2156525d6576d21c4dcaddfa427fae887ef89a7a9de5cbfe0728b3aafa78427e", + "sha256:446014523bb9be5c28128c4d2a10ad6bb60769e78bd85658fe44a450674e0ef8" ], - "version": "==2.3.3" + "version": "==2.3.4" }, "psutil": { "hashes": [ - "sha256:206eb909aa8878101d0eca07f4b31889c748f34ed6820a12eb3168c7aa17478e", - "sha256:649f7ffc02114dced8fbd08afcd021af75f5f5b2311bc0e69e53e8f100fe296f", - "sha256:6ebf2b9c996bb8c7198b385bade468ac8068ad8b78c54a58ff288cd5f61992c7", - "sha256:753c5988edc07da00dafd6d3d279d41f98c62cd4d3a548c4d05741a023b0c2e7", - "sha256:76fb0956d6d50e68e3f22e7cc983acf4e243dc0fcc32fd693d398cb21c928802", - "sha256:828e1c3ca6756c54ac00f1427fdac8b12e21b8a068c3bb9b631a1734cada25ed", - "sha256:a4c62319ec6bf2b3570487dd72d471307ae5495ce3802c1be81b8a22e438b4bc", - "sha256:acba1df9da3983ec3c9c963adaaf530fcb4be0cd400a8294f1ecc2db56499ddd", - "sha256:ef342cb7d9b60e6100364f50c57fa3a77d02ff8665d5b956746ac01901247ac4" + "sha256:028a1ec3c6197eadd11e7b46e8cc2f0720dc18ac6d7aabdb8e8c0d6c9704f000", + "sha256:503e4b20fa9d3342bcf58191bbc20a4a5ef79ca7df8972e6197cc14c5513e73d", + "sha256:863a85c1c0a5103a12c05a35e59d336e1d665747e531256e061213e2e90f63f3", + "sha256:954f782608bfef9ae9f78e660e065bd8ffcfaea780f9f2c8a133bb7cb9e826d7", + "sha256:b6e08f965a305cd84c2d07409bc16fbef4417d67b70c53b299116c5b895e3f45", + "sha256:bc96d437dfbb8865fc8828cf363450001cb04056bbdcdd6fc152c436c8a74c61", + "sha256:cf49178021075d47c61c03c0229ac0c60d5e2830f8cab19e2d88e579b18cdb76", + "sha256:d5350cb66690915d60f8b233180f1e49938756fb2d501c93c44f8fb5b970cc63", + "sha256:eba238cf1989dfff7d483c029acb0ac4fcbfc15de295d682901f0e2497e6781a", + "sha256:968b1d0ad79ee376f52875b9cf3a204ebb099da50a3567daba8fb890679ce6b9" ], "index": "pypi", - "version": "==5.6.2" + "version": "==5.6.3" }, "pyyaml": { "hashes": [ - "sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c", - "sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95", - "sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2", - "sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4", - "sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad", - "sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba", - "sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1", - "sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e", - "sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673", - "sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13", - "sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19" + "sha256:57acc1d8533cbe51f6662a55434f0dbecfa2b9eaf115bede8f6fd00115a0c0d3", + "sha256:588c94b3d16b76cfed8e0be54932e5729cc185caffaa5a451e7ad2f7ed8b4043", + "sha256:68c8dd247f29f9a0d09375c9c6b8fdc64b60810ebf07ba4cdd64ceee3a58c7b7", + "sha256:70d9818f1c9cd5c48bb87804f2efc8692f1023dac7f1a1a5c61d454043c1d265", + "sha256:86a93cccd50f8c125286e637328ff4eef108400dd7089b46a7be3445eecfa391", + "sha256:a0f329125a926876f647c9fa0ef32801587a12328b4a3c741270464e3e4fa778", + "sha256:a3c252ab0fa1bb0d5a3f6449a4826732f3eb6c0270925548cac342bc9b22c225", + "sha256:b4bb4d3f5e232425e25dda21c070ce05168a786ac9eda43768ab7f3ac2770955", + "sha256:cd0618c5ba5bda5f4039b9398bb7fb6a317bb8298218c3de25c47c4740e4b95e", + "sha256:ceacb9e5f8474dcf45b940578591c7f3d960e82f926c707788a570b51ba59190", + "sha256:fe6a88094b64132c4bb3b631412e90032e8cfe9745a58370462240b8cb7553cd" ], "index": "pypi", - "version": "==5.1" + "version": "==5.1.1" }, "six": { "hashes": [ @@ -114,11 +115,11 @@ }, "youtube-dl": { "hashes": [ - "sha256:4b34f9da88a5ba1e4fb3cbf296b13252bc61e3f14ce25bcd8dc90a8bfa55dadd", - "sha256:fe71aa725dc0f86deacdcc91f38492317d53b0ca8ec0f8bddc9961c205b4fea4" + "sha256:41ee1e4247ed3810d9730c54b25ebe4ca19c1ca7373e3de05a3dc8e8884ca475", + "sha256:ea75d8fd5b1343fa29b6d2fd27c8e346ea45fc0091ec22efedd801eb8397aba1" ], "index": "pypi", - "version": "==2019.5.11" + "version": "==2019.7.30" } }, "develop": {} From 43551cd645c903f224b3ed0af342fc1a41aaeac4 Mon Sep 17 00:00:00 2001 From: Luc Sinet Date: Sun, 10 Nov 2019 14:29:45 +0100 Subject: [PATCH 3/8] develop #comment Add Pipenv.lock file to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 52d741a0..b6d01659 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.pyc *~ *.xpi +Pipfile.lock From e1c391b37f057efebe3ca16585b6c7265afcfee8 Mon Sep 17 00:00:00 2001 From: Luc Sinet Date: Sun, 10 Nov 2019 14:31:13 +0100 Subject: [PATCH 4/8] develop #comment Add dependencie update function to startup script. --- OpenCast.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/OpenCast.sh b/OpenCast.sh index 07854755..e5639678 100755 --- a/OpenCast.sh +++ b/OpenCast.sh @@ -33,6 +33,11 @@ function restart() { stop && start } +function update() { + pipenv update + restart +} + function status() { echo -n "$PROJECT_NAME is ... " [ "$(lsof -t -i :2020)" ] && echo "UP" || echo "DOWN" @@ -57,6 +62,9 @@ stop) restart) restart ;; +update) + update + ;; status) status ;; @@ -67,6 +75,6 @@ test) tests ;; *) - echo "Usage: $0 {start|stop|restart|status|logs|test}" + echo "Usage: $0 {start|stop|restart|update|status|logs|test}" ;; esac From 0012b6a20ae13dc23d250368230cf1d08ec9106a Mon Sep 17 00:00:00 2001 From: Luc Sinet Date: Sun, 10 Nov 2019 14:32:30 +0100 Subject: [PATCH 5/8] develop #comment Add dbus-python dep + make env more permissiv on used python version. --- Pipfile | 4 +- Pipfile.lock | 126 --------------------------------------------------- 2 files changed, 1 insertion(+), 129 deletions(-) delete mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile index 8c0f4d44..aacedd64 100644 --- a/Pipfile +++ b/Pipfile @@ -13,6 +13,4 @@ mock = "*" pyyaml = "*" "hurry.filesize" = "*" psutil = "*" - -[requires] -python_version = "3.5" +dbus-python = "*" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index d154c1f6..00000000 --- a/Pipfile.lock +++ /dev/null @@ -1,126 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "6fdd897e6d7d1857576997df206584df38e1a0974ac718654a26cba26f95b646" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.5" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "bottle": { - "hashes": [ - "sha256:1896a33b2c7c5be07491e6789e341f2e9593a0ff024cc0374615118587c81647", - "sha256:e9eaa412a60cc3d42ceb42f58d15864d9ed1b92e9d630b8130c871c5bb16107c" - ], - "index": "pypi", - "version": "==0.12.17" - }, - "dbus-python": { - "hashes": [ - "sha256:abf12bbb765e300bf8e2a1b2f32f85949eab06998dbda127952c31cb63957b6f" - ], - "version": "==1.2.8" - }, - "decorator": { - "hashes": [ - "sha256:86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", - "sha256:f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6" - ], - "version": "==4.4.0" - }, - "evento": { - "hashes": [ - "sha256:2644a88e4e0a395f0d372ce0b5627506cb29547788bdd97e8fb2240dfe341b13" - ], - "version": "==1.0.1" - }, - "hurry.filesize": { - "hashes": [ - "sha256:f5368329adbef86accd3bc9490522340bb79260455ae89b1a42c10f63801b9a6" - ], - "index": "pypi", - "version": "==0.9" - }, - "mock": { - "hashes": [ - "sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3", - "sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8" - ], - "index": "pypi", - "version": "==3.0.5" - }, - "omxplayer-wrapper": { - "hashes": [ - "sha256:1c535bf15198b7172919dc806c5804503527723a0c4288bf50f6187997d5f50d", - "sha256:8d01aa9d09d908c8af2144224e4a11d0e29f6552524306483b13cfa25f963a17" - ], - "index": "pypi", - "version": "==0.3.2" - }, - "pathlib2": { - "hashes": [ - "sha256:2156525d6576d21c4dcaddfa427fae887ef89a7a9de5cbfe0728b3aafa78427e", - "sha256:446014523bb9be5c28128c4d2a10ad6bb60769e78bd85658fe44a450674e0ef8" - ], - "version": "==2.3.4" - }, - "psutil": { - "hashes": [ - "sha256:028a1ec3c6197eadd11e7b46e8cc2f0720dc18ac6d7aabdb8e8c0d6c9704f000", - "sha256:503e4b20fa9d3342bcf58191bbc20a4a5ef79ca7df8972e6197cc14c5513e73d", - "sha256:863a85c1c0a5103a12c05a35e59d336e1d665747e531256e061213e2e90f63f3", - "sha256:954f782608bfef9ae9f78e660e065bd8ffcfaea780f9f2c8a133bb7cb9e826d7", - "sha256:b6e08f965a305cd84c2d07409bc16fbef4417d67b70c53b299116c5b895e3f45", - "sha256:bc96d437dfbb8865fc8828cf363450001cb04056bbdcdd6fc152c436c8a74c61", - "sha256:cf49178021075d47c61c03c0229ac0c60d5e2830f8cab19e2d88e579b18cdb76", - "sha256:d5350cb66690915d60f8b233180f1e49938756fb2d501c93c44f8fb5b970cc63", - "sha256:eba238cf1989dfff7d483c029acb0ac4fcbfc15de295d682901f0e2497e6781a", - "sha256:968b1d0ad79ee376f52875b9cf3a204ebb099da50a3567daba8fb890679ce6b9" - ], - "index": "pypi", - "version": "==5.6.3" - }, - "pyyaml": { - "hashes": [ - "sha256:57acc1d8533cbe51f6662a55434f0dbecfa2b9eaf115bede8f6fd00115a0c0d3", - "sha256:588c94b3d16b76cfed8e0be54932e5729cc185caffaa5a451e7ad2f7ed8b4043", - "sha256:68c8dd247f29f9a0d09375c9c6b8fdc64b60810ebf07ba4cdd64ceee3a58c7b7", - "sha256:70d9818f1c9cd5c48bb87804f2efc8692f1023dac7f1a1a5c61d454043c1d265", - "sha256:86a93cccd50f8c125286e637328ff4eef108400dd7089b46a7be3445eecfa391", - "sha256:a0f329125a926876f647c9fa0ef32801587a12328b4a3c741270464e3e4fa778", - "sha256:a3c252ab0fa1bb0d5a3f6449a4826732f3eb6c0270925548cac342bc9b22c225", - "sha256:b4bb4d3f5e232425e25dda21c070ce05168a786ac9eda43768ab7f3ac2770955", - "sha256:cd0618c5ba5bda5f4039b9398bb7fb6a317bb8298218c3de25c47c4740e4b95e", - "sha256:ceacb9e5f8474dcf45b940578591c7f3d960e82f926c707788a570b51ba59190", - "sha256:fe6a88094b64132c4bb3b631412e90032e8cfe9745a58370462240b8cb7553cd" - ], - "index": "pypi", - "version": "==5.1.1" - }, - "six": { - "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" - ], - "version": "==1.12.0" - }, - "youtube-dl": { - "hashes": [ - "sha256:41ee1e4247ed3810d9730c54b25ebe4ca19c1ca7373e3de05a3dc8e8884ca475", - "sha256:ea75d8fd5b1343fa29b6d2fd27c8e346ea45fc0091ec22efedd801eb8397aba1" - ], - "index": "pypi", - "version": "==2019.7.30" - } - }, - "develop": {} -} From ccf7b2be14778c493d81c0b7f0d562b19507ee58 Mon Sep 17 00:00:00 2001 From: Luc Sinet Date: Wed, 13 Nov 2019 17:17:29 +0100 Subject: [PATCH 6/8] develop #comment Rewrite and test new setup scripts. - Add systemd configuration for making opencast a service startable at boot time. --- OpenCast.sh | 71 +++++++++++++++++++---------------- README.md | 5 +-- dist/opencast.service | 13 +++++++ setup.sh | 86 ++++++++++++++++++++++++++----------------- 4 files changed, 107 insertions(+), 68 deletions(-) create mode 100644 dist/opencast.service diff --git a/OpenCast.sh b/OpenCast.sh index e5639678..43b9a0b2 100755 --- a/OpenCast.sh +++ b/OpenCast.sh @@ -5,19 +5,40 @@ PROJECT_NAME="OpenCast" LOG_DIR="log" LOG_FILE="$PROJECT_NAME.log" +function is_server_running() { + lsof -t -i :2020 +} + +function wait_for_server() { + while [ ! "$(is_server_running)" ]; do + sleep 1 + done +} + +function element_in() { + local e match="$1" + shift + for e; do [[ "$e" == "$match" ]] && return 0; done + return 1 +} + function start() { - if [ "$(lsof -t -i :2020)" ]; then + if [ "$(is_server_running)" ]; then echo "$PROJECT_NAME server is already running." return fi - echo "Checking for updates." - cd "$PROJECT_DIR" || exit 1 + if [[ "$1" == "-u" ]]; then + update + fi - git pull + cd "$PROJECT_DIR" || exit 1 mkdir -p "$LOG_DIR" + echo "Starting $PROJECT_NAME server." pipenv run python -m "$PROJECT_NAME" & + + wait_for_server pid="$(pgrep -f "python -m $PROJECT_NAME")" echo "$pid" >"$PROJECT_DIR/$PROJECT_NAME.pid" } @@ -34,8 +55,9 @@ function restart() { } function update() { - pipenv update - restart + echo "Checking for updates." + + (cd "$PROJECT_DIR" && git pull && pipenv update) } function status() { @@ -52,29 +74,14 @@ function tests() { pipenv run python -m unittest discover -v -p "*_test.py" } -case "$1" in -start) - start - ;; -stop) - stop - ;; -restart) - restart - ;; -update) - update - ;; -status) - status - ;; -logs) - logs - ;; -test) - tests - ;; -*) - echo "Usage: $0 {start|stop|restart|update|status|logs|test}" - ;; -esac +COMMANDS=("start" "stop" "restart" "update" "status" "logs" "tests") +if element_in "$1" "${COMMANDS[@]}"; then + COMMAND="$1" + shift + "$COMMAND" "$@" +else + echo "Usage: $0 {$( + IFS='|' + echo "${COMMANDS[*]}" + )}" +fi diff --git a/README.md b/README.md index b3fcea51..94a499cf 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,12 @@ Other features: #### Install - Clone this project and enter the Pi-Opencast directory. - Execute the setup script and follow the instructions. -- Reboot your Raspberry-Pi (recommended). #### Use -First of all, note the IP address of your Raspberry-Pi by executing `sudo ifconfig`. +First of all, note the
of your Raspberry-Pi by executing `sudo ifconfig`. ###### Computer -Open your favorite browser and go on `IP-address:2020` +Open your favorite browser and go on `
:2020` ### License diff --git a/dist/opencast.service b/dist/opencast.service new file mode 100644 index 00000000..97fc08cb --- /dev/null +++ b/dist/opencast.service @@ -0,0 +1,13 @@ +[Unit] +Description="OpenCast systemd service" +After=network.target + +[Service] +Type=simple +RemainAfterExit=true +TimeoutStartSec=infinity +User={ USER } +ExecStart={ START_COMMAND } + +[Install] +WantedBy=multi-user.target diff --git a/setup.sh b/setup.sh index f74d39fc..668a710e 100755 --- a/setup.sh +++ b/setup.sh @@ -4,7 +4,10 @@ USER="$(whoami)" PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT="$(basename "$PROJECT_DIR")" INTERNAL_NAME="$(echo "$PROJECT" | cut -f2 -d'-')" +SERVICE_NAME="${INTERNAL_NAME,,}" +SYSTEMD_CONFIG_DIR="/etc/systemd/system/" +# Log an info message. function info() { local yel="\\033[1;33m" local nc="\\033[0m" @@ -12,6 +15,7 @@ function info() { echo -e "$yel>>$nc $1" } +# Log an error message and exit. function error() { local red="\\033[0;31m" local nc="\\033[0m" @@ -20,39 +24,55 @@ function error() { exit 1 } -# Get the user to install as -# shellcheck disable=SC2039 -read -r -p "Install $PROJECT as ? (default:$USER): " u -[ ! -z "$u" ] && USER="$u" +# Check if the script is executed from the project or standalone. +# Clone the project and update PROJECT_DIR accordingly is executed in standalone mode. +function setup_environment() { + if ! git ls-files --error-unmatch "$0" >/dev/null 2>&1; then + local homedir -HOMEDIR="$(getent passwd "$USER" | cut -d: -f6)" -if [ -z "$HOMEDIR" ]; then - error "User '$USER' does not exist." - exit 1 -fi - -# Download project -info "Downloading $PROJECT in $HOMEDIR" -git clone "https://github.com/Tastyep/$PROJECT" "$HOMEDIR/$PROJECT" - -# Install dependencies -info "Installing apt dependencies..." -sudo apt-get update -sudo apt-get install -y lsof python-pip || - error "failed to install dependencies" -info "Installing pip dependencies..." -python -m pip install --user pipenv || - error "failed to install pipenv" - -# Configure boot options -info "Adding to startup options (/etc/rc.local)" -# Add to rc.local startup -sudo sed -i /"exit 0"/d /etc/rc.local -printf "su %s -c '$HOMEDIR/$PROJECT/$INTERNAL_NAME.sh start'\\nexit 0\\n" "$USER" 2>&1 | sudo tee -a /etc/rc.local >/dev/null - -# Starting OpenCast -info "Starting $PROJECT" -chmod +x "$HOMEDIR/$PROJECT/$INTERNAL_NAME.sh" -"$HOMEDIR/$PROJECT/$INTERNAL_NAME.sh" start + homedir="$(getent passwd "$USER" | cut -d: -f6)" + PROJECT_DIR="$homedir/$PROJECT" + + # Download project + info "Cloning $PROJECT into $PROJECT_DIR" + git clone "https://github.com/Tastyep/$PROJECT" "$PROJECT_DIR" + fi +} + +# Format and install the systemd config file. +function start_at_boot() { + info "Setting up startup at boot" + + local config="$PROJECT_DIR/dist/$SERVICE_NAME.service" + sed -i "s/{ USER }/$USER/g" "$config" + sed -i "s#{ START_COMMAND }#$PROJECT_DIR/$INTERNAL_NAME.sh start -u#g" "$config" + sudo cp "$config" "$SYSTEMD_CONFIG_DIR" + sudo systemctl daemon-reload + sudo systemctl enable "$SERVICE_NAME" +} + +# Install dependencies. +function install_deps() { + info "Installing dependencies..." + + sudo apt-get update + sudo apt-get install -y lsof python-pip || + error "failed to install dependencies" + sudo -H pip install -U pipenv || + error "failed to install pipenv" +} + +# Start the server as daemon. +function launch() { + info "Starting $PROJECT" + + chmod +x "$PROJECT_DIR/$INTERNAL_NAME.sh" + sudo systemctl start "$SERVICE_NAME" +} + +setup_environment +install_deps +start_at_boot +launch exit 0 From b8effb8b5e4064aea28a077f38a318d73f204867 Mon Sep 17 00:00:00 2001 From: Luc Sinet Date: Wed, 13 Nov 2019 17:23:19 +0100 Subject: [PATCH 7/8] develop #comment Log unchaught errors to file before exiting. --- OpenCast/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/OpenCast/__init__.py b/OpenCast/__init__.py index 679b3abe..1be276de 100644 --- a/OpenCast/__init__.py +++ b/OpenCast/__init__.py @@ -18,7 +18,13 @@ def _real_main(argv): config.load_from_file('{}/config.ini'.format(app_path)) s = Server() - s.run() + try: + s.run() + except Exception as e: + logger = logging.getLogger(__name__) + logger.debug("opencast stopped: {}".format(str(e))) + finally: + pass def main(argv=None): From 4db6a5a76d5e25c5247be92511f783dff181c497 Mon Sep 17 00:00:00 2001 From: Luc Sinet Date: Wed, 13 Nov 2019 17:23:45 +0100 Subject: [PATCH 8/8] develop #comment Format code using yapf. --- OpenCast/config.py | 15 +++++-------- OpenCast/controller.py | 13 ++++++----- OpenCast/download_logger.py | 14 +++++------- OpenCast/player_wrapper.py | 43 ++++++++++++++++++------------------ OpenCast/server.py | 15 ++++++++----- OpenCast/video.py | 15 ++++++++----- OpenCast/video_downloader.py | 25 ++++++++++++--------- 7 files changed, 74 insertions(+), 66 deletions(-) diff --git a/OpenCast/config.py b/OpenCast/config.py index db6d6b1f..d4bf40fa 100644 --- a/OpenCast/config.py +++ b/OpenCast/config.py @@ -6,8 +6,8 @@ class Singleton(type): def __call__(cls, *args, **kwargs): if cls not in cls._instances: - cls._instances[cls] = super( - Singleton, cls).__call__(*args, **kwargs) + cls._instances[cls] = super(Singleton, + cls).__call__(*args, **kwargs) return cls._instances[cls] @@ -59,10 +59,8 @@ def _load_cache(self): parser = self._parser[key] for entry_name in dir(category): - if ( - entry_name.startswith('__') or - not self._parser.has_option(key, entry_name) - ): + if (entry_name.startswith('__') + or not self._parser.has_option(key, entry_name)): continue self._parse_entry(parser, category, entry_name) @@ -78,9 +76,8 @@ def _parse_entry(self, parser, category, entry_name): value = parser.getboolean(entry_name, fallback=entry) else: value = parser.get(entry_name, fallback=entry) - if (type(entry) is str and - (value.startswith(("'", '"')) and - value[0] is value[-1])): + if (type(entry) is str and (value.startswith( + ("'", '"')) and value[0] is value[-1])): value = value[1:-1] setattr(category, entry_name, value) diff --git a/OpenCast/controller.py b/OpenCast/controller.py index fed3b328..58b2d96a 100644 --- a/OpenCast/controller.py +++ b/OpenCast/controller.py @@ -62,12 +62,13 @@ def shift_subtitle(self, increase): def change_volume(self, increase): self._player.change_volume(increase) - logger.debug("[controller] change player volume, increase: {}" - .format(increase)) + logger.debug( + "[controller] change player volume, increase: {}".format(increase)) def seek_time(self, forward, long): - logger.debug("[controller] seek video time, forward={}, long={}" - .format(forward, long)) + logger.debug( + "[controller] seek video time, forward={}, long={}".format( + forward, long)) self._player.seek(forward, long) # Getter methods @@ -91,8 +92,8 @@ def _queue_video(self, video, dl_callback, first): playlist_id = uuid.uuid4() urls = self._downloader.extract_playlist(video.url) videos = [Video(u, playlist_id) for u in urls] - logger.debug("[controller] playlist url unfolded to {}" - .format(videos)) + logger.debug( + "[controller] playlist url unfolded to {}".format(videos)) self._downloader.queue(videos, dl_callback, first) else: logger.debug("[controller] queue single video: {}".format(video)) diff --git a/OpenCast/download_logger.py b/OpenCast/download_logger.py index c76ad26d..abbdb80c 100644 --- a/OpenCast/download_logger.py +++ b/OpenCast/download_logger.py @@ -24,21 +24,19 @@ def log_download(self, d): def _log_download_info(self, d): filename = d.get('filename', 'unknown') - self._logger.info("[downloader] {} | {} | {}" - .format(filename, - self._format_ratio(d), - self._format_speed(d))) + self._logger.info("[downloader] {} | {} | {}".format( + filename, self._format_ratio(d), self._format_speed(d))) def _log_download_error(self, d): filename = d.get('filename', 'unknown') - self._logger.error("[downloader] error downloading {}: {}" - .format(filename, d)) + self._logger.error("[downloader] error downloading {}: {}".format( + filename, d)) def _log_download_finished(self, d): filename = d.get('filename', 'unknown') total = d.get('total_bytes', 0) - self._logger.info("[downloader] finished downloading {} ({})" - .format(filename, size(total))) + self._logger.info("[downloader] finished downloading {} ({})".format( + filename, size(total))) def _format_ratio(self, d): downloaded = d.get('downloaded_bytes', None) diff --git a/OpenCast/player_wrapper.py b/OpenCast/player_wrapper.py index 1bc5ff9c..38000adc 100644 --- a/OpenCast/player_wrapper.py +++ b/OpenCast/player_wrapper.py @@ -112,6 +112,7 @@ def _stop_impl(self, stop_browsing=False): def is_stopped(): return not self.playing() + if not self._sync(5000, 500, is_stopped): logger.error("[player] cannot stop") @@ -177,14 +178,14 @@ def impl(): def seek(self, forward, long): if forward: - if long: # Up arrow, + 5 minutes + if long: # Up arrow, + 5 minutes self._exec_command('seek', 300) - else: # Right arrow, + 30 seconds + else: # Right arrow, + 30 seconds self._exec_command('seek', 30) else: - if long: # Down arrow, - 5 minutees + if long: # Down arrow, - 5 minutees self._exec_command('seek', -300) - else: # Left arrow, - 30 seconds + else: # Left arrow, - 30 seconds self._exec_command('seek', -30) def playing(self): @@ -210,19 +211,20 @@ def _make_player(self, video): command += ['--blank'] for tries in range(5): - logger.debug("[player] opening {} with opt: {}".format(video, command)) + logger.debug("[player] opening {} with opt: {}".format( + video, command)) try: - self._player = self._player_factory(video.path, - command, - 'org.mpris.MediaPlayer2.omxplayer1', - self._on_exit) + self._player = self._player_factory( + video.path, command, 'org.mpris.MediaPlayer2.omxplayer1', + self._on_exit) return True except SystemError: logger.error("[player] couldn't connect to dbus") # Kill instance if it is a dbus problem for proc in psutil.process_iter(): if "omxplayer" in proc.name(): - logger.debug("[player] killing process {}".format(proc.name())) + logger.debug("[player] killing process {}".format( + proc.name())) proc.kill() return False @@ -234,8 +236,9 @@ def _play(self): self._prev_impl() if self._history.browsing(): - logger.debug("[player] picking video from history at index ({})" - .format(self._history.index())) + logger.debug( + "[player] picking video from history at index ({})".format( + self._history.index())) video = self._history.current_item() self._history.next() else: @@ -260,15 +263,15 @@ def _play(self): def _play_videos(self): def should_play(): def impl(): - logger.debug("[player] should_play: playing: {}, play_next: {}, qSize: {}, browsing: {}, loop {}, hSize: {}" - .format(self.playing(), self._play_next, len(self._queue), self._history.browsing(), config.loop_last, self._history.size())) + logger.debug( + "[player] should_play: playing: {}, play_next: {}, qSize: {}, browsing: {}, loop {}, hSize: {}" + .format(self.playing(), self._play_next, len(self._queue), + self._history.browsing(), config.loop_last, + self._history.size())) return (self._stopped or (not self.playing() and self._play_next and (len(self._queue) > 0 or self._history.browsing() or - (config.loop_last and self._history.size() > 0) - ) - ) - ) + (config.loop_last and self._history.size() > 0)))) logger.debug("[player] should_play()") f = self._executor.submit(impl) @@ -307,9 +310,7 @@ def _on_exit(self, player, code): def player_factory(): def make_player(path, command, dbus_name, exit_callback): - player = OMXPlayer(Path(path), - command, - dbus_name=dbus_name) + player = OMXPlayer(Path(path), command, dbus_name=dbus_name) player.exitEvent += exit_callback return player diff --git a/OpenCast/server.py b/OpenCast/server.py index 5ccfb6ec..86c4908b 100755 --- a/OpenCast/server.py +++ b/OpenCast/server.py @@ -33,13 +33,18 @@ def run(self): serverConfig = config.config['Server'] self._set_routes() - logger.info("[server] started on {}:{}" - .format(serverConfig.host, serverConfig.port)) - run(self._app, host=serverConfig.host, port=serverConfig.port, - reloader=False, debug=True, quiet=True) + logger.info("[server] started on {}:{}".format(serverConfig.host, + serverConfig.port)) + run(self._app, + host=serverConfig.host, + port=serverConfig.port, + reloader=False, + debug=True, + quiet=True) def _set_routes(self): - self._app.route('/static/', name='static', + self._app.route('/static/', + name='static', callback=self._serve_file) self._app.route('/', callback=self._remote) self._app.route('/remote', callback=self._remote) diff --git a/OpenCast/video.py b/OpenCast/video.py index 69ed5123..8c8a7219 100644 --- a/OpenCast/video.py +++ b/OpenCast/video.py @@ -22,14 +22,17 @@ def __init__(self, url, playlist_id=None): def __repr__(self): title = '' if self._title is None else str(self._title) - playlist_id = '' if self._playlist_id is None else str(self._playlist_id) - return str({'title': title, - 'url': str(self._url), - 'playlist_id': playlist_id}) + playlist_id = '' if self._playlist_id is None else str( + self._playlist_id) + return str({ + 'title': title, + 'url': str(self._url), + 'playlist_id': playlist_id + }) def __eq__(self, other): - return (self._url == other._url and - self._playlist_id == other._playlist_id) + return (self._url == other._url + and self._playlist_id == other._playlist_id) @property def url(self): diff --git a/OpenCast/video_downloader.py b/OpenCast/video_downloader.py index 0b975779..2dc0235b 100644 --- a/OpenCast/video_downloader.py +++ b/OpenCast/video_downloader.py @@ -58,8 +58,9 @@ def extract_playlist(self, url): # NOTE(specific) youtube specific base_url = url.split('/playlist', 1)[0] - urls = [base_url + '/watch?v=' + entry['id'] - for entry in data['entries']] + urls = [ + base_url + '/watch?v=' + entry['id'] for entry in data['entries'] + ] return urls def _download_queued_videos(self): @@ -82,16 +83,17 @@ def _download(self, video, dl_callback): return video.title = data['title'] - video.path = config.output_directory + '/' + video.title + '.mp4' + video.path = "{}/{}-{}.mp4".format(config.output_directory, + video.title, hash(video.url)) def download_hook(d): self._logger.log_download(d) - logger.debug("[downloader] starting download for: {}" - .format(video.title)) + logger.debug("[downloader] starting download for: {}".format( + video.title)) options = { 'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/' - 'bestvideo+bestaudio/best', + 'bestvideo+bestaudio/best', 'debug_printtraffic': self._log_debug, 'noplaylist': True, 'merge_output_format': 'mp4', @@ -103,8 +105,8 @@ def download_hook(d): try: ydl.download([video.url]) except Exception as e: - logger.error("[downloader] error downloading '{}': {}" - .format(video, str(e))) + logger.error("[downloader] error downloading '{}': {}".format( + video, str(e))) return logger.debug("[downloader] video downloaded: {}".format(video)) @@ -113,7 +115,7 @@ def download_hook(d): def _fetch_metadata(self, url, options): logger.debug("[downloader] fetching metadata") options.update({ - 'ignoreerrors': True, # Causes ydl to return None on error + 'ignoreerrors': True, # Causes ydl to return None on error 'debug_printtraffic': self._log_debug, 'logger': logger }) @@ -122,8 +124,9 @@ def _fetch_metadata(self, url, options): try: return ydl.extract_info(url, download=False) except Exception as e: - logger.error("[downloader] error fetch metadata for '{}': {}" - .format(url, str(e))) + logger.error( + "[downloader] error fetch metadata for '{}': {}".format( + url, str(e))) return None