From 39f99371d4bfb74fb9d988960dac6a61354bf4d2 Mon Sep 17 00:00:00 2001 From: eat-sleep-code Date: Mon, 26 Feb 2024 19:49:20 -0700 Subject: [PATCH] Implement PID and SSH checks, add autostart setup script. Remove option for running as systemd service as it does not work with VLC. --- README.md | 27 +--- install-tiny-tv.service.sh | 12 -- install-tiny-tv.sh | 13 +- setup-autostart-tiny-tv.sh | 13 ++ tiny-tv.py | 282 +++++++++++++++++++------------------ tiny-tv.service | 13 -- 6 files changed, 163 insertions(+), 197 deletions(-) delete mode 100644 install-tiny-tv.service.sh create mode 100644 setup-autostart-tiny-tv.sh delete mode 100644 tiny-tv.service diff --git a/README.md b/README.md index f78e93d..e38b80c 100644 --- a/README.md +++ b/README.md @@ -54,23 +54,6 @@ tiny-tv-update # reinstalls Tiny TV program + _--loop_ : Set whether video plays continuously in a loop *(default: True)* + _--shuffle_ : Set whether category-based playback is shuffled *(default: False)* ---- - -### Playback Controls - -You can control the device from a Bluetooth keyboard. - -|Key|Action| -|---|---| -| - | Decrease volume (in increments of 5%) | -| + | Increase volume (in increments of 5%) | -| Space | Pause/Resume | -| q | Exit | -| Left | Restart current video | -| Right | Advanced to next video | - - - --- ### Examples @@ -134,15 +117,11 @@ defaults.pcm.card 1 ## Autostart Tiny TV Want to start the Tiny TV program every time you boot your Raspberry Pi? Here is how! -### Via *.profile* file -* Add a line to the very end of ~/.profile such as `tiny-tv 'category' --category 'cartoons'` and save the changes to the file. -* Restart the Tiny TV. -*Note that this method avoids many permissions issue which may arise when attemptint to use the **systemd** method below. However, it will attempt to start the Tiny TV program every time you login to via SSH, etc. You may need to press CTRL-C to abort the loading of the application in these instances.* -### Via systemd: -* Review `/etc/systemd/system/tiny-tv.service` +* Review the `AUTOSTARTCOMMAND` command in `~/tiny-tv/setup-autostart-tiny-tv.sh` * If you would like to add any of aforementioned options you may do so by editing the service file. -* Run `~/tiny-tv/install-tiny-tv.service.sh` +* Run `~/tiny-tv/setup-autostart-tiny-tv.sh` +* Restart the Tiny TV. > [!TIP] diff --git a/install-tiny-tv.service.sh b/install-tiny-tv.service.sh deleted file mode 100644 index 65b418c..0000000 --- a/install-tiny-tv.service.sh +++ /dev/null @@ -1,12 +0,0 @@ -# This script will install the service to start the Tiny TV when the Raspberry Pi is started. -cd ~ -echo -e '' -echo -e '\033[32mTiny TV Service [Installation Script] \033[0m' -echo -e '\033[32m-------------------------------------------------------------------------- \033[0m' -echo -e '' -echo -e '\033[93mEnabling Service... \033[0m' -sudo systemctl enable tiny-tv.service - -echo '' -echo -e '\033[93mStarting Service... \033[0m' -sudo systemctl start tiny-tv.service diff --git a/install-tiny-tv.sh b/install-tiny-tv.sh index 136b588..28c2549 100644 --- a/install-tiny-tv.sh +++ b/install-tiny-tv.sh @@ -22,9 +22,10 @@ sudo chown -R $USER:$USER /home/pi/tiny-tv-venv echo -e '\033[93mInstalling Python libraries... \033[0m' -sudo /home/pi/tiny-tv-venv/bin/pip3 install pynput python-vlc yt-dlp rpi_backlight --force +sudo /home/pi/tiny-tv-venv/bin/pip3 install python-vlc yt-dlp rpi_backlight python-pidfile --force echo 'SUBSYSTEM=="backlight",RUN+="/bin/chmod 666 /sys/class/backlight/%k/brightness /sys/class/backlight/%k/bl_power"' | sudo tee -a /etc/udev/rules.d/backlight-permissions.rules + echo '' echo -e '\033[93mProvisioning logs... \033[0m' sudo mkdir -p /home/pi/logs @@ -35,6 +36,7 @@ sudo mount -a sudo chown -R $USER:$USER /home/pi/logs sudo systemctl daemon-reload + echo '' echo -e '\033[93mInstalling Tiny TV... \033[0m' cd ~ @@ -42,15 +44,8 @@ sudo rm -Rf ~/tiny-tv sudo git clone https://github.com/eat-sleep-code/tiny-tv sudo chown -R $USER:$USER tiny-tv cd tiny-tv -sudo chmod +x tiny-tv.py -sudo chmod +x backlight.py - -echo '' -echo -e '\033[93mCreating Service... \033[0m' -sudo mv tiny-tv.service /etc/systemd/system/tiny-tv.service -sudo chown root:root /etc/systemd/system/tiny-tv.service sudo chmod +x *.sh -echo 'Please see the README file for more information on configuring the service.' + cd ~ echo '' diff --git a/setup-autostart-tiny-tv.sh b/setup-autostart-tiny-tv.sh new file mode 100644 index 0000000..c45dbcf --- /dev/null +++ b/setup-autostart-tiny-tv.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# This script will set Tiny TV to autostart when a local bash session starts. + +AUTOSTARTCOMMAND=tiny-tv 'category' --category 'cartoons' --shuffle True --volume 0 + + +# ============================================================================== +cd ~ +echo '' +echo -e '\033[93mSetting up autostart... \033[0m' +sudo touch ~/.profile +sudo sed -i '/\b\(tiny-tv\)\b/d' ~/.profile +sudo sed -i "\$a$AUTOSTARTCOMMAND" ~/.profile \ No newline at end of file diff --git a/tiny-tv.py b/tiny-tv.py index 7603cd1..819afa3 100644 --- a/tiny-tv.py +++ b/tiny-tv.py @@ -1,24 +1,24 @@ from datetime import datetime from backlight import BacklightControl from functions import Echo, Console -from pynput.keyboard import Key, Listener from time import sleep import argparse import glob import os +import pidfile import random import signal import shutil import subprocess import sys -import threading import vlc import yt_dlp -version = '2024.02.25' +version = '2024.02.26' os.environ['TERM'] = 'xterm-256color' +##print(os.environ) console = Console() echo = Echo() @@ -41,6 +41,7 @@ input = args.input or '' +pidFilePath = '/home/pi/tiny-tv/tiny-tv.pid' # ------------------------------------------------------------------------------ @@ -107,15 +108,6 @@ # === Keyboard Watcher ======================================================== -def watchForKeyPress(): - try: - with Listener(on_press=handleKeyPress) as listener: - listener.join() - except: - pass - finally: - listener.stop() - def handleKeyPress(key): global instance global player @@ -165,7 +157,7 @@ def getVideoPath(inputPath): try: os.makedirs(inputPath, exist_ok = True) except OSError: - print ('\n ERROR: Creation of the output folder ' + inputPath + ' failed!' ) + console.error('Creation of the output folder ' + inputPath + ' failed!' ) echo.on() quit() else: @@ -181,19 +173,22 @@ def playVideo(videoFullPath): try: player = instance.media_player_new() media = instance.media_new(videoFullPath) - print('Playing' + videoFullPath) - player.set_media(media) - player.audio_set_volume(volume) + console.info('Playing' + videoFullPath) isPaused = False - backlight.fadeOn() - player.play() - sleep(1.5) - duration = player.get_length() / 1000 - sleep(duration) - backlight.fadeOff() + if "SSH_CONNECTION" in os.environ: + console.warn('Tiny TV launched from an SSH session. Video(s) will not be displayed.') + else: + player.set_media(media) + player.audio_set_volume(volume) + backlight.fadeOn() + player.play() + sleep(1.5) + duration = player.get_length() / 1000 + sleep(duration) + backlight.fadeOff() return True except Exception as ex: - print ('\n ERROR: ' + str(ex)) + console.error(str(ex)) return False @@ -201,128 +196,137 @@ def playVideo(videoFullPath): try: os.chdir('/home/pi') - backlight.off() - print('\n Tiny TV ' + version ) - print('\n ----------------------------------------------------------------------\n') - print('\n Press [Ctrl]-C to exit. \n') - - input = input.strip() - if input.find('.') == -1 and input.find(';') == -1 and input.lower() != 'category': - input = input + '.mp4' - video = input - - - # --- YouTube Download ------------------------------------------------- - - if video.lower().find('youtube.com') != -1: - print(' Starting download of video... ') - downloadHeight = 720 - if maximumVideoHeight >= 4320: # Future product - downloadHeight = 4320 - elif maximumVideoHeight >= 2160: # Minimum Raspberry Pi 4B - downloadHeight = 2160 - elif maximumVideoHeight >= 1080: # Minimum Raspberry Pi 3B+ - downloadHeight = 1080 - - try: - youtubeDownloadOptions = { - 'outtmpl': videoCategoryFolder + '%(id)s.%(ext)s', - 'format': 'best[height=' + str(downloadHeight) + ']' - } - with yt_dlp.YoutubeDL(youtubeDownloadOptions) as youtubeDownload: - info = youtubeDownload.extract_info(video) - video = info.get('id', None) + '.' + info.get('ext', None) - except Exception as ex: - console.info(' Falling back to best quality video... ') - youtubeDownloadOptions = { - 'outtmpl': videoCategoryFolder + '%(id)s.%(ext)s', - 'format': 'best' - } - with yt_dlp.YoutubeDL(youtubeDownloadOptions) as youtubeDownload: - info = youtubeDownload.extract_info(video) - video = info.get('id', None) + '.' + info.get('ext', None) - pass - - console.info(' Setting the owner of the file to current user...') - try: - shutil.chown(videoCategoryFolder + video, user='pi', group='pi') - except: - pass - - if saveAs.lower() != 'youtube-id': - try: - os.rename(videoCategoryFolder + video, videoCategoryFolder + saveAs) - video = saveAs - except Exception as ex: - console.info(str(ex)) + console.print('\n Tiny TV ' + version ) + console.print('----------------------------------------------------------------------') + console.print('\n Press [Ctrl]-C to exit. \n') - # --- Pillar Box / Letter Box Removal ---------------------------------- - - if removeVerticalBars == True: - console.info(' Starting removal of vertical black bars (this will take a while)... ') - subprocess.call('ffmpeg -i "' + videoCategoryFolder + video + '" -filter:v "crop=ih/3*4:ih,scale=-2:' + str(maximumVideoHeight) + ',setsar=1" -c:v libx264 -crf ' + str(quality) + ' -preset veryfast -c:a copy "' + videoCategoryFolder + '~' + video + '"' , shell=True) - os.remove(videoCategoryFolder + video) - os.rename(videoCategoryFolder + '~' + video, videoCategoryFolder + video) - try: - shutil.chown(videoCategoryFolder + video, user='pi', group='pi') - except: - pass - - elif removeHorizontalBars == True: - console.info(' Starting removal of horizontal black bars (this will take a while)... ') - subprocess.call('ffmpeg -i "' + videoCategoryFolder + video + '" -filter:v "crop=iw:iw/16*9,scale=-2:' + str(maximumVideoHeight) + ',setsar=1" -c:v libx264 -crf ' + str(quality) + ' -preset veryfast -c:a copy "' + videoCategoryFolder + '~' + video + '"', shell=True) - os.remove(videoCategoryFolder + video) - os.rename(videoCategoryFolder + '~' + video, videoCategoryFolder + video) - try: - shutil.chown(videoCategoryFolder + video, user='pi', group='pi') - except: - pass - + try: + with pidfile.PIDFile(pidFilePath): + backlight.off() + input = input.strip() + if input.find('.') == -1 and input.find(';') == -1 and input.lower() != 'category': + input = input + '.mp4' + video = input + + + # --- YouTube Download ------------------------------------------------- + + if video.lower().find('youtube.com') != -1: + console.info(' Starting download of video... ') + downloadHeight = 720 + if maximumVideoHeight >= 4320: # Future product + downloadHeight = 4320 + elif maximumVideoHeight >= 2160: # Minimum Raspberry Pi 4B + downloadHeight = 2160 + elif maximumVideoHeight >= 1080: # Minimum Raspberry Pi 3B+ + downloadHeight = 1080 + + try: + youtubeDownloadOptions = { + 'outtmpl': videoCategoryFolder + '%(id)s.%(ext)s', + 'format': 'best[height=' + str(downloadHeight) + ']' + } + with yt_dlp.YoutubeDL(youtubeDownloadOptions) as youtubeDownload: + info = youtubeDownload.extract_info(video) + video = info.get('id', None) + '.' + info.get('ext', None) + except Exception as ex: + console.info(' Falling back to best quality video... ') + youtubeDownloadOptions = { + 'outtmpl': videoCategoryFolder + '%(id)s.%(ext)s', + 'format': 'best' + } + with yt_dlp.YoutubeDL(youtubeDownloadOptions) as youtubeDownload: + info = youtubeDownload.extract_info(video) + video = info.get('id', None) + '.' + info.get('ext', None) + pass + + console.info(' Setting the owner of the file to current user...') + try: + shutil.chown(videoCategoryFolder + video, user='pi', group='pi') + except: + pass + + if saveAs.lower() != 'youtube-id': + try: + os.rename(videoCategoryFolder + video, videoCategoryFolder + saveAs) + video = saveAs + except Exception as ex: + console.info(str(ex)) + - # --- Resize only ------------------------------------------------------ - - if resize == True and removeVerticalBars == False and removeHorizontalBars == False: - console.info(' Starting resize to maximum video height (this will take a while)... ') - subprocess.call('ffmpeg -i "' + videoCategoryFolder + video + '" -filter:v "scale=-2:' + str(maximumVideoHeight) + ',setsar=1" -c:v libx264 -crf ' + str(quality) + ' -preset veryfast -c:a copy "' + videoCategoryFolder + '~' + video + '"' , shell=True) - os.remove(videoCategoryFolder + video) - os.rename(videoCategoryFolder + '~' + video, videoCategoryFolder + video) - try: - shutil.chown(videoCategoryFolder + video, user='pi', group='pi') - except: - pass - - - # --- Playback --------------------------------------------------------- - - playCount = 0 - keyWatcherThread = threading.Thread(target=watchForKeyPress) - keyWatcherThread.start() - backlight.off() - while playCount >= 0: - playCount += 1 - console.info('\n Starting playback (' + str(playCount) + ') at ' + datetime.now().strftime('%Y-%m-%d %H:%M:%S') + ' ...') - - if (video.lower() == 'category'): - videosToPlay = glob.glob(videoCategoryFolder + '**/*.mp4', recursive = True) - if shuffle == True: - random.shuffle(videosToPlay) - for videoFullPath in videosToPlay: - playVideo(videoFullPath) - else: - videoFullPath = videoCategoryFolder + str(video) - playVideo(videoFullPath) - if loop == False: - break - else: - sleep(1.5) - backlight.on() - sys.exit(0) + # --- Pillar Box / Letter Box Removal ---------------------------------- + + if removeVerticalBars == True: + console.info(' Starting removal of vertical black bars (this will take a while)... ') + subprocess.call('ffmpeg -i "' + videoCategoryFolder + video + '" -filter:v "crop=ih/3*4:ih,scale=-2:' + str(maximumVideoHeight) + ',setsar=1" -c:v libx264 -crf ' + str(quality) + ' -preset veryfast -c:a copy "' + videoCategoryFolder + '~' + video + '"' , shell=True) + os.remove(videoCategoryFolder + video) + os.rename(videoCategoryFolder + '~' + video, videoCategoryFolder + video) + try: + shutil.chown(videoCategoryFolder + video, user='pi', group='pi') + except: + pass + + elif removeHorizontalBars == True: + console.info(' Starting removal of horizontal black bars (this will take a while)... ') + subprocess.call('ffmpeg -i "' + videoCategoryFolder + video + '" -filter:v "crop=iw:iw/16*9,scale=-2:' + str(maximumVideoHeight) + ',setsar=1" -c:v libx264 -crf ' + str(quality) + ' -preset veryfast -c:a copy "' + videoCategoryFolder + '~' + video + '"', shell=True) + os.remove(videoCategoryFolder + video) + os.rename(videoCategoryFolder + '~' + video, videoCategoryFolder + video) + try: + shutil.chown(videoCategoryFolder + video, user='pi', group='pi') + except: + pass + + + # --- Resize only ------------------------------------------------------ + + if resize == True and removeVerticalBars == False and removeHorizontalBars == False: + console.info(' Starting resize to maximum video height (this will take a while)... ') + subprocess.call('ffmpeg -i "' + videoCategoryFolder + video + '" -filter:v "scale=-2:' + str(maximumVideoHeight) + ',setsar=1" -c:v libx264 -crf ' + str(quality) + ' -preset veryfast -c:a copy "' + videoCategoryFolder + '~' + video + '"' , shell=True) + os.remove(videoCategoryFolder + video) + os.rename(videoCategoryFolder + '~' + video, videoCategoryFolder + video) + try: + shutil.chown(videoCategoryFolder + video, user='pi', group='pi') + except: + pass + + + # --- Playback --------------------------------------------------------- + + playCount = 0 + backlight.off() + while playCount >= 0: + playCount += 1 + console.info('Starting playback (' + str(playCount) + ') at ' + datetime.now().strftime('%Y-%m-%d %H:%M:%S') + ' ...') + + if (video.lower() == 'category'): + videosToPlay = glob.glob(videoCategoryFolder + '**/*.mp4', recursive = True) + if shuffle == True: + random.shuffle(videosToPlay) + for videoFullPath in videosToPlay: + playVideo(videoFullPath) + else: + videoFullPath = videoCategoryFolder + str(video) + playVideo(videoFullPath) + if loop == False: + break + else: + sleep(1.5) + + os.remove(pidFilePath) + backlight.on() + sys.exit(0) + except pidfile.AlreadyRunningError: + console.error('Another instance of Tiny TV is already running. Exiting...') + echo.on() + sleep(1) + sys.exit(0) + except Exception: + pass + except KeyboardInterrupt: - player.stop() backlight.on() echo.on() - sleep(1) sys.exit(0) diff --git a/tiny-tv.service b/tiny-tv.service deleted file mode 100644 index 3892b0d..0000000 --- a/tiny-tv.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=Tiny TV Service - -[Service] -ExecStart=/home/pi/tiny-tv-venv/bin/python3 /home/pi/tiny-tv/tiny-tv.py 'category' --category 'cartoons' --shuffle True --volume 0 -Restart=always -StandardOutput=syslog -StandardError=syslog -SyslogIdentifier=Tiny TV -User=pi - -[Install] -WantedBy=multi-user.target