#!/bin/sh ############################################################################# gDate=240320 gVers=029 ############################################################################# # Aerialist 1.29 # # Video screensaver plugin for xscreensaver # # AppleTV online or cached videos # YouTube/Dailymotion etc via yt-dlp/youtube-dl and Seasons # # Intermittent title/time overlay # Resume where the video was last interrupted # # Crontab like selection of season # See "STABLE" below # (e.g. Fireplace at night in Winter, Places, Animals by weekday) # # Parameters that are "fun" are up front # Customize by putting overrides into ~/.config/aerialist # in shell format - or call script with 'set' or 'settings' to edit # # (p) Public Domain, 2023-24 by Greg Kerr ############################################################################# # # * Usage - For Terminal invocation or called by xscreensaver # # aerialist [cmds][parameters]... # # Cmd Action # --- ------ # install install into .xscreensaver file, offer to pre-cache videos # set Edit ~/.config/aerialist settings # seasons List seasons # vid [s] List videos for a season # cron List time table for which videos to play, show current # test [p] Run a test with just this video # # sound Set volume to 25% # volume [v] Set volume to this %age # speed [s] Set speed to this rate (1=normal,2-9 = /10, 10+ = /100, decimal=rate) # aerial [p] Path to find folders of videos (aka Seasons) # movies [p] Path to videos - (no seasons) # season [p] Fixed season # id [p] Show video names & ids for a given path # # (For AppleTV video selection / cache) # online Use ATV Videos via web only # 4k Use 4k versions of ATVs # hdr Use HDR versions of ATVs # offline Use Cached videos only # # Call with no parameters to test with logging to terminal # Normally called from xscreensaver to enhance your idle screen ## ############################################################################# # # # Global Parameters # # # # Options - override in ~/.config/aerialist with shell format script ONLINE=1 # Allow network video play - set to 2 to FORCE online ONLY ONLINE4K=0 # Online 4K option (vs 2K if available) ONLINEHDR=0 # Online HDR option (vs SDR if available) CACHE=2 # 1 = Look in Cache (AERIAL), 2 = Download videos while playing) TEST=0 # Just show the video that would play AERIAL=~/Videos/Aerialist # Master Path of MOVIES folders (required if ONLINE == 0) RESUME=1 # Resume video playback from last position when interrupted SOUND=0 # 0-100 audio volume, set in ~/.config/aerial if you wish videos with sound or in STABLE SPEED=1 # Speed of video playback PANSCAN=1.0 # Crop 16:9 movies - set to 0 to not do it FORMATS='' # List of formats to play / not play - e.g. -AV1 HEVC to disallow AV1 explicitly allow HEVC (implicitly all others) - requires mediainfo installed # Example FORMATS='-AV1 -VP9' for MacBookPro11,2 / i7-4770HQ (not enough GPU power) SUBINTERVAL=2 # Description every this many minutes SUBREPEATS=55 # Number of times to repeat @ SUBINTERVAL TEXTSIZE=4 # %age of screen pixels for label TEXTRGB=AA998877 # Text Color # Add your own seasons and times to play - link # Format - Weekday (can be multiple) MonthDay-Begin M-D-End 24HourBeg 24HourEnd AudioVolume PlaybackRate Weight SeasonName STABLE=" 6 1-1 1-31 17 18 0 1.0 0 MyVideos Example - Silent Sunday Evening In January from MyVideos Collection $STABLE" # Add your own video seasons - format Season= id id id id BITES=" MyVideos= BtoopYV_HdM dmo:x8g7rts $BITES " # A player will be chosen as available if this is empty - but you can override with preferred player PLAYER='' # Empty to use mpv -> mplayer -> vlc PLARGS='' # Any additional player arguments ONLINETPT='http' # Transport to get online assets (http or https) ############################################################################ # End of User interesting section #################################################### # # Season Table - what videos to play by date/time # (Northern Hemishpere Timed/Named) # # WD BDate EDate BH EH Vo PS We Name STABLE=' * 12-1 2-14 19 7 22 75 0 Fireplace * * * 19 7 11 1 0 Night * * * 16 18 1 5 90 Sports 6 * * 12 16 13 1 90 DIY 06 * * 16 20 19 1 75 Music 135 * * 8 15 11 5 90 Animals 56 * * 13 17 5 5 75 Trains * * * 9 17 9 5 75 Places * 12-1 2-29 * * 15 1 99 Winter * 3-1 5-31 * * 15 1 99 Spring * 6-1 8-31 * * 15 1 99 Summer * 9-1 11-30 * * 15 1 99 Autumn * * * * * 11 0.6 10 Apple ' # Video sites - last one is default if no prefix on video ID # One can add any yt-dlp supported site here with prefix - then use that # prefix in the BITES table below whose Season Title should be put in # the STABLE above VITES=' dmo:www.dailymotion.com/video/ vmo:vimeo.com/ yap:yandex.com/video/preview/ ytb:www.youtube.com/watch?v= ' # Online Video IDs - format Season=id id id id Season2= id id id id # Where id is [source:]videoid and default source is youtube - see VITES # Comments may be used after a # character # To add new season(s) in settings extend or replace this BITES=' Animals= Wb14e21CN5Y s7DbVTkaXn0 NyelTWkAMUE yYoWnLCvYo0 DVTRklHhEsU ZPyQh7jjbpE qOXeIip16No eoTpdTU8nTA asTC8fvw0W8 3qBfTWuFjbo Uu5R1ENCmTY # Capybaras for Keanu Fireplace= mKCieTImjvU 6j3hPg0t5fo B0j__sZXiWI 7pTdGR46ZKA eYy-ahKKMys paiSoYQhaBE KzmtBgqap0s wNwMnGRjgdw bmGsQkLb4yg Night= B5unCXpegAw o7w1jq29oZU bCEMBU9Sk98 SiryvrStb8E -SMKVJO_wGo pRROyjwdNjA 3Ow0ET-ob3E GX7xOqLgkTM 3G1KBu6H6BM UV07v89nf3g 44KvHwRHb3A JpyPwnDaijU zdOTV2RH9IY pOBrBb0gV64 jvTAPzS8glw HTeNfkAoUlI REDVbTQxMXo vSwz4n24koY _Sx8NX2TsqI lpmAs7FhOs4 y1doKCK7Dwk qgfd-uWTVwg bCEMBU9Sk98 B5unCXpegAw DhNMS5mD4ak 3Qn7_mljjTg TfqaEJpQmDc veCr5M0rNpo lL5RPnXyklk pyu-djhQFVU 6zh-LN-dObY DslTxXsDLsw gosjiD288Jk N-FwVPT0BSk 9uZ8CCa0t4Y NN-PE_qiokw yY2xRxycjDE AwIu-UwEtRI MN98aki1Guk -xyvrFc2i6Y MiCa3ub1iK8 Places= Ee0Qh_nIoHw # Afrika CHSnz0bCaUk # Alaska gq2bvwrERUc # Angola YDL8HbY9ENU # Argentina bQmzk05I3nw # Australia eaLWXfsuxf0 # Bali Vmx6I1vQFSs # Bavaria jpZaVpjWbbI # Caribbean Jgegx5BH5xM # Chile -TXh2rrNshI # China gig9-jO7YL4 # Earth EBsnEiDTqEQ # Egypt 0EdVsuDq28M # Europe dmo:x7vvcvj # Florida 6lxg0NKZjOE # France 8_j64iq_hTE # Germany li-_BLtq58w # Germany II 3nu_PVydBfs # Germany Munich rFVpSwgCkCo # Hawaii 8SO273jh_LI # India Q8O9vUG4d2s gCouE441TW4 # India - Chennai ycDLfQ1Cv_Y # Ireland 56gZuTy6xIc # Italy # aaiLCfShqG0 # Italy - Rome 2hr E4c21evjzFc # Italy - Rome 1hr G5RpJwCJDqc # Japan JexgGtA00H8 # Japan - Nature Cjl70Cqo3Q4 # Kashmir -s0vUJamgEw # Namibia gCRNEJxDJKM # Nepal vtxVK3sbZ0o # New Zealand dmo:x7x1xw3 # North Carolina CxwJrzEdw1U # Norway jjP12kUYUs4 # Philippines mTm0WKWGpas # Saudi Arabia qEjzVTo2eTs # Slovenia 8K VzqWO5EaSps # South Africa NGoCQ3HyxQs # Sri Lanka kVxTrhojpFI # Switzerland DEAcPVg8V7U # Tanzania hXa-WgLCyok # Thailand _tYThjKtBKg TYfYX0ngLkE # Ukraine Kyiv pre-Putin megalomania kmG0kifKaFs # Ukraine Odessa pre-Putin destruction 4Z9mUjtFJYY # USA 16Xaqxiggw0 oAZdQFc7iHM w8LLarFIy3k WRLhiPv2VD0 # USA - Cohasset, MA fNETOWpWc_Q LyZCSP6SkfQ 5V323pJeUQI OG4PZtp6epQ rdhEGgwJAYA # USA - Indialantic & Naples, FL eaokcXVFLlc # USA - Winston Salem, NC oVnwgZV4MCk # USA - One Ring to Rule Them All _K-Gtb_ywfg # Venezuela 64bA0CxioCE # Vietnam Ihiy9A__QM8 # Wonders dmo:x8g7rts # Zanzibar Trains= _O1eGD8XbTg # Africa - Coppertrail YhAfiuqt5zE # France - Chamonix to Montenvers dp0PgoELHk0 # Germany - Berlin ujm_3XB-IYc # Hungary Győr to Veszprém m2lC_JG6zdk # India yq6KXZ78p-M # India - Palakkad, Kerala iNfcXZVRjns # India - Ooty to Coonoor njTPvmcPUGs # 150+ Trains Part 1 zomZywCAPTA # Montenegro SBd3C9vEkJM # Russia - TSE YRyLAR6A1Wc # Switzerland Gkr5_sz_uGM # USA - Alaska - Skagway MK_RazmDMaA # USA - Appalachian Foothills YjF2pk19A7Q # USA - Blue Ridge Scenic Railway k9VpqUeI-mk # USA - NH Sports= wj_ZEDISBnA I6VpZ3bnnKI o7W7OvETO40 plt8tdGyygI lMbN6IcrxLs JEtYYtwX1sM 5X57DS7cvHs yL-4vY9BWL0 7jz0pXSe_kI mTM1_m48IcA hEGQZeBBvsA CAs0DjbH92s kE7D7qFayVg _uKWgZpUjYU zd4EIXDjEJ4 nnWtfMRuIdE BFJ6vbwAsgI z1qLQ7P8_Yk hwDI4cbioLw xLoImWFNBiA bg3XO_991VE -mFOj3Vfm1A FQgWc1_cLfk bcwnLnooA5g yaVnhuXRIx4 KxllkdTTs1M o7W7OvETO40 NR7YyJrj0c8 vQopnwLFdXc 2giTb7IpJDU nwpX4zEW1wc tx1-fDueJxg D7v4VAB1NoY J2v2k06Rp-U rC4UVSqi0qc 9nuGI9fnOME V55DRE9vJSs 5oBlBwd9HeA zrbpDg1xHSU MQVxhgdzI2U 3HxnHPJu2fs 5adBuaBpvag 1hPkXcXeY4w WL1kRhx-Q6U F_P9nMhSj64 Winter= ZyCudosHxUA 3PXUO_7Pj5o WrBmEymjbmU Q4Ht5wd9Iec UmhbcBClFm8 EE38xxyJW5I Spring= UZ9uyQI3pF0 ikn24Lvyb5U LLk0rwWq-E8 Y88Nm7RsZAk hOdhtImnb5Y Summer= o4O_KoXeCFI kBfawqIMgYI RhHNcTJv9fY Bpe4ptn2jCI -l9E7OAexQM p9wjJYVTdfk Autumn= jeKABSyMlTU 6qxeqGEFDt4 qJDK-kdRZJE lN9LdcAQnjw xqDuf48WyzI tOAwkxO2Uj8 Music= YbBU06irWT8 n2MtEsrcTTs B9FzVhw8_bY JSR5ETrEmIU 6iAVZr7aAJQ djV11Xbc914 sBzrzS1Ag_g rMqayQ-U74s kHLHSlExFis JF8BRvqGCNs p7FCgw_GlWc wfN4PVaOU5Q rP9Z5Pc8cRM G4LIM9VYhB0 c18441Eh_WE Zzyfcys1aLM QpbHdIrtpNo 6yP4Nm86yk0 5NPBIwQyPWE 8Ilh1ewceco 5anLPw0Efmo kXYiU_JCYtU eVTXPUF4Oz4 fV4DiAyExN0 qMxX-QOV9tI iP6XpLQM2Cs KlyXNRrsk4A TUVcZfQe-Kw G7KNmW9a75Y C-u5WLJ9Yk4 LOZuxwVk7TU 9uWwvQKGjLI KwIe_sjKeAY IxkJHX7ukKE tDl3bdE3YQA DIY= EIGcemiOT4M ZsE7IR0Ty74 JAJFQw8Vo-o EIGcemiOT4M -207fdj7Wuo 0CdG6qkGfm8 ' # If title of video should be changed from default truncated filename / ATV description # If lower case of pre: is found in default title, post: used as title RETITLE=' b2-2:Kamehameha Childhood Hideout? b2-4:San Francisco Dawn b4-3:Golden Gate Bridge from San Francisco 396B_1105:Somewhere in Italy? 401c_1037:Ireland and Britain by Night from ISS? CH_C007_C011:China West of Xi`an? DB_D001_C005:Dubai Harbor? LA_A006_C008:LAX MEX_A006_C008:Republicans meeting in Mexico? PA_A010_C007:Democrats meeting in PA? TH_804_A001:Thailand (aka Paradise) Undersea? 1223LV_FL:Maybe Chicago? GMT026:Korea and Japan at Night from Space? GMT110:Aurora Antarctica GMT312:Africa at Night from Space? arthursseat:Arthur`s Seat, Edinburgh, UK big-sur:Big Sur, California hampstead:London, UK from Hampstead northstrip:Las Vegas Strip (North) pecos_fog:Foggy Pecos, Texas, US portland:Portland, Oregon, US ' # Globals MPID=-1 # Player pid TESTME='' # Fake a selection with test XYZ (use 'test' 'VideoPath') SEASON='Apple' # The collection of videos under AERIAL that we will use - chosen from STABLE according to time/date CHOICE='' # The chosen video CTITLE='' # Display name of chosen video CIDENT='' # ID of chosen video CFORM='' # Encoding Format of chosen video (if FORMATS != '') LOCKF='' # Lock file PSPEED='' PSOUND='' # User chosen speed & sound overrides # Options oSeason='' oMovies='' # Markers OS_TYPE=$(uname -s) USER=$(id -un) USID=$(id -u) LOGF="/tmp/${0##*/}.$USER.log" # XDG [ -z "$XDG_CONFIG_HOME" ] && XDG_CONFIG_HOME=~/'.config' [ ! -d "$XDG_CONFIG_HOME" ] && mkdir -p "$XDG_CONFIG_HOME" [ -z "$XDG_RUNTIME_DIR" ] && XDG_RUNTIME_DIR="/var/run/user/$USID" [ ! -d "$XDG_RUNTIME_DIR" ] && { mkdir -p "$XDG_RUNTIME_DIR" 2>/dev/null [ ! -d "$XDG_RUNTIME_DIR" ] && XDG_RUNTIME_DIR="/tmp/run-user-$USID" mkdir -p "$XDG_RUNTIME_DIR" } # Allow defaults override in .config/aerialist PREFS="$XDG_CONFIG_HOME/aerialist" [ -e "$PREFS" ] && . "$PREFS" # If RESUME != 0 STATEDB="$XDG_CONFIG_HOME/aerialist-state" # Auto choice of player for i in 'mpv' 'mplayer' 'vlc'; do [ -z "$PLAYER" ] && which "$i" > /dev/null 2>&1 && PLAYER="$i" PLARGS='' done # Modern youtube-dl? YTDL='' for i in 'yt-dlp' 'youtube-dl'; do [ -z "$YTDL" ] && which "$i" >/dev/null 2>&1 && YTDL="$i" done Msg() { echo "$(date +%k:%M:%S) $@" >&2; } Log() { [ $TEST -eq 0 ] && Msg "$@" >> "$LOGF" 2>&1 || Msg "$@" 2>&1 | tee -a "$LOGF" >&2; } Dbg() { [ $TEST -ne 0 ] && Msg "+++ [$@] +++"; } Err() { Msg "*** [$@] ***"; } Throw() { Err "$1"; [ -n "$2" ] && exit $2; exit 44; } Prompt() { OUT='' if [ $TEST -ne 0 ]; then Msg "$@" printf '\n to continue (q to quit): '; read OUT [ "$OUT" = 'q' ] && Throw 'Manual abort' 42 else Log "$@" fi } SeasonMatch() { # Process SEASON table (on stdin) and echo first matched Season - or $1 if no match - forked so everything local really local d m w H M pw pdb pde phb phe pS pmb pme egb snd spd x Xs pCmt M=$(date "+%d %m %w %H %M"); Dbg "Season.: $M" d="${M%% *}"; M="${M#* }" m="${M%% *}"; M="${M#* }" w="${M%% *}"; M="${M#* }" H="${M%% *}"; M="${M#* }"; H="${H#0}"; M="${M#0}" Xs=0 # To do - calculate weight and return for a re-run? while read pw pdb pde phb phe snd spd x pS pCmt; do if [ -n "$pw" ]; then # Old format support - inserted weight in 1.2, default it to 1, inserted speed in 1.3, default it to 1 [ -z "$pS" ] && pS="$x" && x=1 [ -z "$pS" ] && pS="$spd" && spd=1 # Dbg "Season?: [W:$pw][D:$pdb[$pde][T:$phb][$phe][S:$pS][D:$d/$m W:$w T:$H/$M]" >&2 # * Matches anything if [ "$pw" = '*' -o "${pw#*$w}" != "$pw" ]; then # Weekday is all or contains today (0-6) pmb="${pdb%-*}"; pdb="${pdb#*-}" pme="${pde%-*}"; pde="${pde#*-}" # Dbg "Season-: [$pmb[$pme][$pdb][$pde]" dm=0 if [ "$pmb" = '*' -o "$pme" = '*' ]; then dm=1 else # If at start month, >= start day ... if Left to right, inside, else outide [ \( $m -eq $pmb -a $d -ge $pdb \) \ -o \( $m -eq $pme -a $d -le $pde \) \ -o \( $pmb -lt $pme -a $m -gt $pmb -a $m -lt $pme \) \ -o \( $pmb -gt $pme -a \( $m -gt $pmb -o $m -lt $pme \) \) ] && dm=1 fi # If match so far, check hour range if [ $dm -ne 0 ]; then dm=0 if [ "$phb" = '*' -o "$phe" = '*' ]; then dm=1 else [ \( $phb -le $phe -a $H -ge $phb -a $H -le $phe \) \ -o \( $phb -ge $phe -a \( $H -ge $phb -o $H -le $phe \) \) ] && dm=1 fi [ $dm -ne 0 ] && echo "$snd:$spd:$pS" && return 0 fi fi fi; done [ -n "$1" ] && echo "$1" || echo "$pS" # Default if no match (provided or last one in table) } ChooseMovies() # Set MOVIES & VIDEODB { local i # Default / User Option to override SEASON="$1" [ -n "$oMovies" ] && { MOVIES="$oMovies"; SEASON="${oMovies##*/}"; } || MOVIES="${AERIAL}/${SEASON}" # Some alternative locations for MOVIES if it not a directory - or chosen based on Season election if [ ! -d "$MOVIES" ]; then MOVIES='' for i in "${AERIAL}/${SEASON}" "${AERIAL%/*}/$SEASON" ~/"Videos"/Aerial*/"$SEASON" \ '/home'/*/"VIDEO"/Aerial*/"$SEASON" ~/"Videos/$SEASON" '/home'/*/"VIDEO/$SEASON" \ "/SHARE/VIDEO/COLLECTION/$SEASON" "/SHARE/DRONE/$SEASON"; do [ -d "$i" ] && MOVIES="$i" && Dbg "Selected: $i" done fi # No folder found? if [ -z "$MOVIES" ]; then MOVIES="$AERIAL/$SEASON" Download "$SEASON" 'cache' elif [ ! -e "$AERIAL" ]; then # If we have found them elsewhere; convenience link in e.g. ~/Videos Dbg "Link: $MOVIES to $AERIAL" ln -s "$MOVIES" "$AERIAL" fi # Continue unfinished downloads until folder count matches # CacheOne "$MOVIES" "$SEASON" # End with / for find MOVIES="${MOVIES%/}" Dbg "Movies: $MOVIES" # database files to allow for no repeats when playing videos # Subtitle to show occasional text VIDEODB="$XDG_CONFIG_HOME/aerialist-${SEASON}" SUBTITLE="$XDG_CONFIG_HOME/aerialist-${SEASON}.srt" } ChooseSeason() # Set SEASON { local s skip p # Get Season / Sound / Speed if [ -n "$1" ]; then s="$1" elif [ $ONLINE -gt 1 ]; then s='Apple' Dbg 'Online forced' else [ -n "$STABLE" ] && s=$(echo "$STABLE" | SeasonMatch "$SEASON") SOUND="${s%%:*}"; s="${s#*:}" SPEED="${s%%:*}"; s="${s#*:}" # User (parameter) override? [ -n "$PSOUND" ] && SOUND="$PSOUND" [ -n "$PSPEED" ] && SPEED="$PSPEED" fi Dbg "Season: $s" ChooseMovies "$s" } VideoSite() { local s for s in $VITES; do [ "${s%%:*}" = "$1" ] && OUT="${s#*:}" && return 0 done OUT="${s#*:}" } VideoBites() { # Return IDs of videos for season in $1 - or list of seasons if $1 is empty local x s cs echo "$BITES" | while read x; do if [ -n "$x" ]; then x="${x%%#*}" [ "$x" != "${x#*=}" ] && s="${x%%=*}" && [ -n "$s" ] && cs="$s" && [ -z "$1" ] && echo "$cs" [ "$cs" = "$1" ] && echo "${x#*=}" fi; done } FileSize() { case "$OS_TYPE" in 'Linux') stat -Lc %s "$1" ;; *) stat -Lf %z "$1" ;; esac } VideoID() { local f i s d # Given path with a name like Desc-Name-ID.ext or Desc-Name [ID].ext or Desc-Name.ext # Return ID in OUT, Desc in OU2, Name in OU3 (ID = size if none in name, ID may have - if gt 11 chars) f="${1##*/}" # Drop path f="${f%.*}" i="${f%]*}" if [ "$f" = "$i" ]; then # No bracketed ID i="${f#*-}" if [ "$i" != "$f" -a ${#i} -gt 4 ]; then while [ ${#i} -gt 11 -a "$i" != "${i#*-}" ]; do i="${i#*-}" # ID might have '-' in it anywhere done f="${f%-$i}" elif [ -s "$1" ]; then i='='$(FileSize "$1") fi AppleDesc "$f" && d="$OUT" else i="${i##*[}" f="${f%[*}" f="${f% }" fi [ "$f" != "${f#*-}" ] && s="${f%-*}" && [ ${#s} -gt 3 ] && d="$s" && f="${f#$d-}" [ -z "$d" ] && d="$f" && f='' [ -z "$i" ] && i="$d" # Use description if no ID in name Dbg "VID: [$i : $d : $f ]" OUT="$i" OU2="$d" OU3="$f" } VideoIDs() { local f TEST=1 Msg "Scanning: $1" echo '_' echo 'ID Desc Name Original' for f in "${1}"/* "$1"; do if [ -s "$f" ]; then VideoID "$f" echo "[$OUT] [$OU2] [$OU3] ${f##*/}" fi; done } Download() { # $1 is Season to download # $2 if 'one' - just download one and return # if 'cache' - download one in background # if 'count' - just return number of videos for this season (to compare e.g. to folder count) in $OUT local line st url n i f v cnt s # Download a Season's content # Prompt "Download entry ($CACHE): $@" cnt=0 if [ "$1" = 'Apple' ]; then Msg "Apple season (Cache: $CACHE)" if [ $CACHE -gt 0 ]; then AppleTVDB if [ "$2" = 'count' ]; then [ -s "$OUT" ] && cnt=$((0 + $(wc -l < "$OUT"))) OUT=$cnt return 0 elif [ $CACHE -gt 1 ]; then mkdir -p "$MOVIES" while read line; do if [ -n "$line" ]; then AppleURL "$line"; st="$OU2"; url="$OUT"; n="${OUT##*/}" Dbg "AppleTV: $st ($url)" for i in "$MOVIES/"*"${n}"; do if [ -s "$i" ]; then # Check bad size from aborted download e.g. s=$(FileSize "$i") [ $s -gt 5678123 ] && n='' || { Log "Removing bad cache: $i"; rm "$i"; } fi; done if [ -n "$n" ]; then if [ "$2" = 'cache' ]; then Prompt "Continue to cache $url as $st-$n in the background" curl -o "${st}-${n}" "$url" & return 2 else Log "Downloading: $url to $MOVIES" curl -o "${st}-${n}" "$url" Log "Downloaded: $n" [ "$2" = 'one' ] && return 1 fi else Log "ATV: ${url##*/} already exists" fi fi; done < "$OUT" fi fi return 0 elif [ -n "$YTDL" -a $CACHE -gt 0 ]; then local vIds v f src vIds=$(VideoBites "$1") [ -z "$vIds" ] && Err "No predefined videos for Season: $1" && return 1 if [ "$2" = 'count' ]; then for v in $vIds; do cnt=$((1 + $cnt)) done OUT="$cnt" return fi if [ $CACHE -gt 1 ]; then mkdir -p "$MOVIES" if [ -d "$MOVIES" ]; then cd "$MOVIES" # yt-dlp names title [id].xyz - youtube-dl names title-id.xyz (where id can contain a '-') for v in $vIds; do src="${v%%:*}" v="${v#$src:}" [ -z "$src" ] && src='YT' for f in *"${v}"*; do if [ -s "$f" ]; then s=$(FileSize "$f") [ $s -gt 5678123 ] || { Log "Removing bad cache: $f"; rm "$f"; f=''; } f="${f%.*}" if [ "$f" != "${f%]*}" ]; then # yt-dlp stashes ID in brackets at end of name f="${f%]*}" f="${f##*[}" else # Old format - video ID (conflict with '-' in video ID from some YT) while [ ${#f} -gt 11 -a "${f}" != "${f#*-}" ]; do f="${f#*-}" # ID might have '-' in it anywhere done fi [ "$f" = "$v" ] && v='' fi; done if [ -n "$v" ]; then VideoSite "$src"; site="$OUT" if [ "$2" = 'cache' ]; then Prompt "Caching: $src ($v in $MOVIES)" "$YTDL" "${ONLINETPT}://${site}${v}" & return 2 else Log "Fetch: $src ($v in $MOVIES)" "$YTDL" "${ONLINETPT}://${site}${v}" Log "Done: $src ($v)" [ "$2" = 'one' ] && return 1 fi else Log "Already have: $f in $1" fi done else Err "$MOVIES could not be created to stash videos" fi fi else Err "No yt-dlp + CACHE setting active to populate $MOVIES for $1" fi [ "$2" = 'count' ] && OUT=$cnt } InstallMe() { local c ic b p q c="${1}" [ ! -x "$c" ] && chmod +x "$c" # In case curl'd from github b="${c##*/}" ic=$(which "$b" 2>/dev/null) if [ "$ic" != "$c" ]; then if [ -n "$ic" -a -w "$ic" ]; then if diff "$ic" "$c" >/dev/null; then Dbg "Already installed as $ic" c="$ic" else Dbg "Installing $c to $ic" cp "$c" "$ic" && c="$ic" fi else p="$PATH":~/.local/bin ic='' while [ -n "$p" -a -z "$ic" ]; do q="${p%%:*}"; p="${p#*:}"; [ "$p" = "$q" ] && p='' Dbg "Checking install to: $q" [ -w "$q" ] && cp "$c" "$q" && ic="${q}/${b}" && Dbg "Installed to $q" done [ -n "$ic" ] && c="$ic" || Err "Could not find a writable place in PATH to install - using $c" fi fi [ ! -x "$c" ] && chmod +x "$c" # In case curl'd from github OUT="$c" } IsInstalled() { local a c a=~/'.xscreensaver' [ ! -s "$a" ] && return 1 c="$0"; [ "$c" != "${c#/}" ] || c="$(pwd)/$c" grep -q "$c" "$a" } Install() { TEST=3 # .xscreensaver does NOT like full path in quotes for program (for possible spaces) local d m w H M pw pdb pde phb phe pS pmb pme egb snd x Xs pCmt local o a c line o=~/'.xscreensaver.pre-aerialist' a=~/'.xscreensaver' c="$0"; [ "$c" != "${c#/}" ] || c="$(pwd)/$c" InstallMe "$c"; c="$OUT" if [ ! -s "$a" ]; then echo "# Created by ${0} on $(date) timeout: 0:22:00 splash: False mode: random programs: \ \"Aerialist\" $c 4k \n\ " > "$a" fi if [ -s "$a" ]; then echo "------------------------------------------------------------------ INSTALL Original: $o Aerialist: $a Command: $c Press to install ${0##*/} into .xscreensaver, Ctrl-C to quit Type any parameters to add when started separated with a space: Type 4k to use 4k videos when available Type hdr to use HDR videos when available Type volume X to set volume to X% when playing Type offline to never use AppleTV online videos Type online to always use AppleTV online videos Type movies PATH to use PATH for video searching ------------------------------------------------------------------ " grep -q "$c" "$a" && echo "$c is already installed in $a, but you can update it. " printf "Parameters: "; read line cp -f "$a" "$o" Msg "Installing [$c] as a screensaver..." if [ -s "$o" ]; then awk -v "PRG=$c $line" 'BEGIN { I=0 } { P=1 if (I) { p=$1 if ("-" == p) p=$2 if (index(p, "Aerial") > 0) P=0 } if (P) print $0 # Funky format of .xscreensaver prefs - why not redone in 6.0?!? ;) if ("programs:" == $1) { print "\t\t\"Aerialist\"\t" PRG "\t\t\t \\n\\" I=1 } }' "$o" > "$a" if diff "$a" "$o" > /dev/null; then echo 'No change.' else echo 'Should be installed, here is the diff: --------------' diff "$a" "$o" echo '-------------- ' fi fi fi # Download videos? [ $CACHE -lt 1 ] && Msg "CACHE is 0 - so no cached videos will be used, only played via the AppleTV service." [ $CACHE -lt 2 ] && Throw "CACHE is less than 2 - so no videos will be downloaded." 21 echo " Needed helpers -------------- Youtube-DL: $(which $YTDL 2>&1) Curl: $(which curl 2>&1) Player: $(which $PLAYER 2>&1) XScreenSaver: $(which xscreensaver 2>&1) Video Root: $AERIAL " Prompt ' Continue to cache all videos (> 1TB) now. Otherwise videos will be downloaded/streamed as needed if you stop now. (Streaming only works with ATV videos, not YouTube.) ' [ $ONLINE -gt 1 ] && ONLINE=1 # Otherwise no downloading echo "$STABLE" | while read pw pdb pde phb phe snd x pS pCmt; do # Old format support - inserted column in 1.2, default it to 0 [ -z "$pS" ] && pS="$x" && x=0 if [ -n "$pS" ]; then Dbg "> __________ > $pS < __________ <" ChooseSeason "$pS" Dbg "Video Path: $MOVIES" Download "$pS" fi done Throw 'Install complete' 0 } Random() { # Return random 0 through $1-1 in $OUT local m="$(od -vAn -N4 -tu4 < /dev/urandom)" m=1$(date '+%S')"${m#*[0-9]}" [ $1 -eq 0 ] && OUT=0 || OUT=$(($m % $1)) Dbg "Random: $OUT" } RandomLine() { # Pick and remove a random line from a file local pick Random $((0 + $(wc -l < "$1"))) pick=$((1 + $OUT)) OUT=$(sed "${pick}q;d" "$1") # Get the line # cp "$1" "${1}.last" case "$OS_TYPE" in 'Darwin'|'FreeBSD') sed -i '' "${pick}d" "$1" ;; *) sed -i "${pick}d" "$1" ;; # Remove the line esac } ApplePit() { # Scan an AppleTV Json file for videos & properties local k c v k2h k2s k4h k4s l t t='unk' while read k c v; do k="${k#\"}" k="${k%%\"*}" case "$k" in 'accessibilityLabel') l="${v#\"}"; l="${l%%\"*}" ;; 'timeOfDay') t="${v#\"}"; t="${t%\"}" ;; 'url-1080-SDR') k2s="${v#*:}"; k2s="${k2s%%\"*}" ;; 'url-1080-HDR') k2h="${v#*:}"; k2h="${k2h%%\"*}" ;; 'url-4K-SDR') k4s="${v#*:}"; k4s="${k4s%%\"*}" ;; 'url-4K-HDR') k4h="${v#*:}"; k4h="${k4h%%\"*}" ;; 'url') k2s="${v#*:}"; k2s="${k2s%%\"*}" ;; # tvos 11 format '}'*) [ -n "$l" -a -n "$k2s" ] && echo "$t:$l:$k2s:$k2h:$k4s:$k4h" && l='' ;; esac done [ -n "$l" -a -n "$k2s" ] && echo "$t:$l:$k2s:$k2h:$k4s:$k4h" } AppleURL() { local tod st k2s k2h k4s k4h c # Return (in OUT) the appropriate slice of pie for a line of AppleURL and choice of HDR, 2K/4K c="$1" tod="${c%%:*}"; c="${c#*:}" # day/night/unk st="${c%%:*}"; c="${c#*:}" # Apple Description k2s="${c%%:*}"; c="${c#*:}" # 2k SDR k2h="${c%%:*}"; c="${c#*:}" # 2k HDR k4s="${c%%:*}"; c="${c#*:}" # 4k SDR k4h="${c%%:*}"; c="$k2s" case "$ONLINE4K$ONLINEHDR" in '01') [ -n "$k2h" ] && c="$k2h" ;; # Could we get display size / HDR ability somehow to auto decide? '10') [ -n "$k4s" ] && c="$k4s" ;; '11') [ -n "$k4h" ] && c="$k4h" ;; esac OUT="${ONLINETPT}:${c}"; OU2="$st" # Dbg "AppleURL: $OUT ($1)" } AppleVideoJson() { # ATVos 10, 11 local u for u in \ 'http://sylvan.apple.com/Aerials/2x/entries.json' \ 'http://a1.phobos.apple.com/us/r1000/000/Features/atv/AutumnResources/videos/entries.json' \ ; do curl "$u" 2>/dev/null | ApplePit done } AppleVideoTar() { # ATVos 12+ local u p for u in \ 'http://sylvan.apple.com/Aerials/resources.tar' \ 'http://sylvan.apple.com/Aerials/resources-13.tar' \ 'http://sylvan.apple.com/Aerials/resources-14.tar' \ 'http://sylvan.apple.com/Aerials/resources-15.tar' \ 'http://sylvan.apple.com/Aerials/resources-16.tar' \ 'http://sylvan.apple.com/Aerials/resources-17.tar' \ ; do ( p="/tmp/Aerialist.$USER.$$" mkdir -p "$p" cd "$p" curl -o 'tv.tar' "$u" 2>/dev/null tar xf 'tv.tar' 'entries.json' 2>/dev/null [ -s 'entries.json' ] && ApplePit < 'entries.json' 2>/dev/null rm -f 'tv.tar' 'entries.json' cd .. rmdir "$p") done } AppleTVDB() { # Download if needed to DB, make a copy to $1 if provided local avu="$XDG_CONFIG_HOME/aerialist-AppleURL" if [ ! -s "$avu" ]; then AppleVideoJson > "$avu" AppleVideoTar >> "$avu" fi [ -s "$avu" -a -n "$1" ] && cp "$avu" "$1" OUT="$avu" } AppleDesc() { # Return in OUT the ATV description for video in $1 if available local avu t n AppleTVDB; avu="$OUT" n="${1%_HEVC*}" n="${n%_2K*}"; n="${n%_4K*}"; n="${n%_HDR*}";n="${n%_SDR*}" OUT='' t=$(sed -n -e "s|/${n}.*||p" "$avu" | head -n 1) if [ -n "$t" ]; then t="${t#*:}" t="${t%%:*}" OUT="$t" return 0 fi return 1 } VideoDuration() { local f if [ "$CHOICE" = "${CHOICE#*//}" ]; then if which 'mediainfo' >/dev/null 2>&1; then f=$(mediainfo --Inform="Video;%Duration%" "$CHOICE") f="${f%%.*}" [ -n "$f" ] && f=$(($f / 1000)) || f=-2 # Seconds else f=-1 fi else # Online video duration? Max ATV in 2024 was 906 seconds ... to do this right, we'd have to know when played to completion... and store that f=909 fi OUT="$f" } FormatIsOK() { local i f # Can't test URL if [ -n "$FORMATS" -a "$CHOICE" = "${CHOICE#*//}" ]; then if which 'mediainfo' >/dev/null 2>&1; then # --Inform="Video;%Format%:%Duration%" ... but not a solution for URLs f=$(mediainfo --Inform="Video;%Format%" "$CHOICE") if [ -n "$f" ]; then Dbg "Format: $f" OUT="$f" for i in $FORMATS; do [ "$i" = "$f" ] && Log "Acceptable: $f" && return 0 [ "${i#-}" = "$f" ] && return 1 done return 0 else Dbg "mediainfo returned no format for: $CHOICE" fi else Dbg "FORMATS is specified as $FORMATS but mediainfo is not installed." fi fi OUT='' } ProposeVideo() { # Select a video according to SEASON - but may be wrong format - return file/url in $CHOICE # Set SEASON, CHOICE, CTITLE, CIDENT local n i hour w f ChooseSeason "$@" # Select right set of videos Dbg "Movie DB: $VIDEODB" # If Empty DB - then find videos in MOVIES or Reset Apple list to AppleTV DB if [ ! -s "$VIDEODB" ]; then if [ "$SEASON" = 'Apple' ]; then AppleTVDB "$VIDEODB" elif [ -d "$MOVIES" -a $CACHE -ne 0 ]; then Dbg "Find: find \"$MOVIES\" -type f -iname '*.mkv' -o -iname '*.mov' -o -iname '*.mp4' -o -iname '*.webm'" find "$MOVIES" -type f -size +2M -iname '*.mkv' -o -iname '*.mov' -o -iname '*.mp4' -o -iname '*.webm' > "$VIDEODB" fi [ -s "$VIDEODB" ] && n=$((0 + $(wc -l < "$VIDEODB"))) || n=0 Dbg "Found: $n videos" # Start a background caching process if videos are less than expected (Download will check if CACHING enabled) Download "$SEASON" 'count'; w=$OUT Dbg "Want: $w videos" [ $n -lt $w ] && Download "$SEASON" 'cache' # No videos available - presume one is downloading and fall back to ATV if [ $n -le 0 -a "$1" != 'Apple' ]; then Dbg "Fall back to ATV" ProposeVideo 'Apple' return fi fi # select at random a video from collection ($SEASON) to play RandomLine "$VIDEODB" [ -n "$TESTME" ] && Msg "Test override: $TESTME" && OUT="$TESTME" && TESTME='' CHOICE="$OUT" [ -z "$CHOICE" ] && Throw "No choice in $VIDEODB" VideoID "$CHOICE"; CIDENT="$OUT"; CTITLE="$OU2" # Find cached/renamed items if [ "$SEASON" = 'Apple' ]; then # Offline (vs AppleTV)? AppleURL "$CHOICE"; CHOICE="$OUT"; CTITLE="$OU2" # Have we cached it? if [ "$CACHE" -ne 0 ]; then for f in "$MOVIES"/*"$CHOICE"*; do [ ! -s "$f" ] && f='' done [ -n "$f" -a -s "$f" ] && CHOICE="$f" && Dbg "Cached: $f" [ -z "$f" ] && Prompt "Caching is ON but $CHOICE is not cached, continue to stream" fi elif [ ! -s "$CHOICE" ]; then Log "Gone: $CHOICE" for f in "$MOVIES"/*"$CIDENT"*; do [ ! -s "$f" ] && f='' done if [ -s "$f" ]; then CHOICE="$f" Log "Subst: $f ($CIDENT)" fi fi } ChooseVideo() { local i i=0 while [ $i -lt 11 ]; do ProposeVideo "$@" if FormatIsOK; then CFORM="$OUT" VideoDescription "$CTITLE"; CTITLE="$OUT" # Allow for overriding file name / Apple description return 0 fi Log "Ignoring format: $OUT from $CHOICE" i=$((1 + $i)) done } VideoDescription() { # Look up some key terms to create a video decription # If video filename (lower cased) matches before ':', # show string after ':' instead of file name local line k v name ToLower "$1"; name="$OUT" OUT=$(echo "$RETITLE" | while read line; do if [ -n "$line" ]; then v="${line#*:}" ToLower "${line%%:*}"; k="$OUT" [ "${name#*$k}" != "$name" ] && echo "$v" fi; done) [ -z "$OUT" ] && OUT="$1" } ToLower() { # [ -n "$BASH_VERSINFO" ] && [ $BASH_VERSINFO -gt 3 ] && OUT="${1,,*}" && return OUT=$(echo "$1" | tr '[:upper:]' '[:lower:]') } Subtitles() { local t u n s f c g f="+%H:%M, %A, %d. %B" case "$OS_TYPE" in 'FreeBSD'|'Darwin') c='-v+' ;; # FreeBSD Date increment *) c='-d@' ;; # NetBSD/Linux Date format at seconds esac t=0; n=1 s=$(date '+%s') while [ $n -lt $SUBREPEATS ]; do # 33 * SUBINTERVAL minutes worth of messages u="${t}" [ ${#u} -eq 1 ] && u="0$u" [ "${c%@}" = "$c" ] && g="${c}${t}M" || g="${c}${s}" g=$(date "$g" "$f") echo "$n 00:${u}:03,142 --> 00:${u}:09,067 $g $1 " t=$(($t + $SUBINTERVAL)) n=$((1 + $n)) # In case some joker makes SUBINTERVAL 0 s=$((60 * $SUBINTERVAL + $s)) done # Dbg "Date Parameters: [$g][$f]" } GetState() { local line OUT='' if [ $RESUME -ne 0 ]; then if [ -s "$STATEDB" ]; then line=$(sed -n -e '/:'"$CIDENT"'$/{p;q}' "$STATEDB") OUT="${line%%:*}" # && Log "State for $CIDENT is $OUT" fi return 0 fi return 1 } Exp() { local n=$2 OUT=1 if [ $1 -ne 0 ]; then while [ $n -gt 0 ]; do OUT=$(($OUT * $1)); n=$(($n - 1)) done; fi } SetState() { local t s line d old db OUT=0 if [ $RESUME -ne 0 ]; then GetState; old=${OUT:-0} if [ -n "$1" ]; then t=$1 else s=$(( $(date +%s) - $STARTAT )) # Seconds played # Reset position if ended quickly (end not detected), else adjust for speed of playback [ $s -lt 3 ] \ && { Log "Quicky reset: $s"; s=$((0 - $old)); } \ || { local p; t="${SPEED#*.}"; [ "$t" = "$SPEED" ] && t=0; Exp 10 ${#t}; p=$OUT; s=$(($s * ${SPEED%.*} + $s * $t / $p)); } t=$(($old + $s - 2)) # Played before + played now + me [ $t -lt 0 ] && t=0 if [ $t -gt 0 ]; then VideoDuration "$CHOICE"; d="$OUT" # Beyond last minute? [ $d -gt 0 -a $t -gt $(($d - 61)) ] && Log "Beyond $OUT: $t" && t=0 fi fi # Save or remove state? Log "States: $old / $t [$CIDENT] $SPEED" if [ $old -ne 0 -o $t -ne 0 ]; then [ -s "$STATEDB" ] && db=$(sed -n -e '/:'"$CIDENT"'$/!p' "$STATEDB") || db='' echo "$db" > "$STATEDB" [ $t -gt 0 ] && echo "$t:$CIDENT" >> "$STATEDB" fi OUT=$t return 0 fi return 1 } IsUp() { local f case "$OS_TYPE" in 'Linux') f='-h -q' ;; *) f='-p' ;; esac ps $f "$1" > /dev/null 2>&1 } Stop() { # Must exit - use for trap TERM INT HUP to exit cleanly local mpid f if [ $MPID -gt 0 -o $TEST -ne 0 ]; then mpid="$MPID" MPID=-1 if [ $mpid -gt 0 ]; then Log "Stop: $mpid" IsUp $mpid && kill $mpid fi SetState [ $mpid -gt 0 ] && sleep 6 && IsUp $mpid && Log "Ninja: $mpid" && kill -9 $mpid else Log 'Player down' cat "$LOCKF" >> "$LOGF" fi mv -f "$LOCKF" "${LOCKF}.old" Throw "${1:-End}" "${2:-0}" } Scale() { # Map $1/$4 onto 1-$2 with $3 digits of precision local n maxs maxd dig o # (e.g. 1 100 4 8 -> 0.125 or 1/8 @ 4 digits) n=$1 maxs=$2 maxd=$3 dig=$4 n=$(($n * $dig * $maxd / $maxs)) # 1 -> 0.080; 100 -> 8.000 o=$(($n % $dig + $dig)) OUT=$((n / $dig)).${o#1} } PlayerArgs() { local subf vol tsiz seek args seek='' [ $RESUME -ne 0 ] && GetState && [ -n "$OUT" ] && seek="$OUT" [ -n "$SPEED" -a "$SPEED" != '1' ] && { [ "$SPEED" = "${SPEED#*.}" ] && [ $SPEED -gt 2 ] && SPEED="$(($SPEED / 100)).$((SPEED % 100))"; } args='' case "$PLAYER" in # Don't mess with screensaver, quiet, full screen, subtitles, seek, 'mplayer') # Sometimes still using 80% cpu - I think not right -vo [ $TEST -eq 0 ] && args="${args} -really-quiet" args="${args} -nostop-xscreensaver -fs -title Aerialist -framedrop" if [ -n "$1" ]; then args="${args} -sub $1" [ -n "$TEXTSIZE" ] && args="${args} -subfont-text-scale $TEXTSIZE -subfont-autoscale 0" [ -n "$TEXTRGB" ] && args="${args} -ass -ass-color $TEXTRGB" fi [ -n "$PANSCAN" ] && args="${args} -panscan $PANSCAN" [ -n "$seek" ] && args="${args} -ss $seek" [ -n "$SPEED" ] && args="${args} --speed=$SPEED" [ -z "$SOUND" -o "$SOUND" = '0' ] && args="${args} -nosound" || args="${args} -volume $SOUND" [ -n "$XSCREENSAVER_WINDOW" ] && args="${args} -really-quiet -wid $XSCREENSAVER_WINDOW" ;; 'mpv') [ $TEST -eq 0 ] && args="${args} --really-quiet" args="${args} --stop-screensaver=no --fs" # Framedrop - auto in mpv args="${args} --hwdec=auto-safe" # auto, auto-safe # args="${args} --vo=gpu" # args="${args} --vo=vdpau" [ -n "$SPEED" ] && args="${args} --speed=$SPEED" [ -n "$SOUND" ] && args="${args} --volume=$SOUND" if [ -n "$1" ]; then args="${args} --sub-file=$1" [ -n "$TEXTSIZE" ] && args="${args} --sub-font-size=$(($TEXTSIZE * 8))" [ -n "$TEXTRGB" ] && args="${args} --sub-color=#$TEXTRGB" fi [ -n "$seek" ] && args="${args} --start=$seek" [ -n "$XSCREENSAVER_WINDOW" ] && args="${args} --wid=$XSCREENSAVER_WINDOW" ;; 'vlc') [ $TEST -eq 0 ] && args="${args} --quiet --no-qt-error-dialogs" args="${args} --no-disable-screensaver -f --no-interact --play-and-exit --started-from-file" if [ -n "$1" ]; then args="${args} --sub-file $1" [ -n "$TEXTSIZE" ] && args="${args} --sub-text-scale $(($TEXTSIZE * 16))" [ -n "$TEXTRGB" ] && args="${args} --freetype-color="$(($(echo "ibase=16; $TEXTRGB" | bc) / 256)) fi [ -n "$seek" ] && args="${args} --start-time $seek" [ -n "$SPEED" ] && args="${args} --rate=$SPEED" [ -z "$SOUND" -o "$SOUND" = '0' ] && args="${args} --no-audio" || { Scale $SOUND 100 4 1000; args="${args} --gain $OUT"; } # Should be 8 - but seems loud [ -n "$XSCREENSAVER_WINDOW" ] && args="${args} --drawable-xid $XSCREENSAVER_WINDOW --no-embedded-video" ;; esac OUT="${args} $PLARGS" } Options() { local i a # Poor mans getopts - Install into xscreensaver and exit, test mode (just output what would happen) while [ -n "$1" ]; do a=0; case "${1#--}" in 'install') Install ;; # Install into Xscreensaver / Download MOVIES 'id'*) VideoIDs "$2"; exit 0 ;; # Test Video IDs 'online') ONLINE=2 ;; # Use AppleTV videos 'offline') ONLINE=0 ;; # ... or do NOT 'sound') PSOUND=25 ;; # Audio 'speed') PSPEED="$2"; a=1 ;; # Playback speed 'volume') PSOUND="$2"; a=1 ;; 'aerial') AERIAL="$2"; a=1 ;; # MOVIES folders inside here 'movies') oMovies="$2"; a=1 ;; # Just use this folder 'season') oSeason="$2"; a=1 ;; # Just use this one 'hdr') ONLINEHDR=1 ;; # AppleTV Video quality '4k') ONLINE4K=1 ;; 'test') TEST=1; TESTME="$2"; a=1 ;; # Test one video file 'seas'*) VideoBites; exit 0 ;; # Show seasons 'vid'*) VideoBites "$2"; exit 0 ;; # Show videos 'cron'*) echo "$STABLE"; TEST=1 ChooseSeason; exit 0 ;; # Show Time Table and current choice 'set'*) for i in 'joe' 'pico' 'nano' 'vi' 'emacs'; do [ -z "$EDITOR" ] && which "$i" > /dev/null 2>&1 && EDITOR="$i" done if [ ! -e "$PREFS" ]; then head -n 1 "$0" > "$PREFS" echo "# # # Aerialist $gVers Preferences # # $(date) # # Remove any lines you do not change (if you like) #" >> "$PREFS" head -n 90 "$0" | tail -n 40 >> "$PREFS" fi exec "$EDITOR" "$PREFS"; Throw 'Complete' 0 ;; esac; shift; [ $a -ne 0 -a -n "$1" ] && shift; done # Are we inside xscreensaver? if [ -z "$XSCREENSAVER_WINDOW" ]; then TEST=2 # Are we installed? if ! IsInstalled; then Prompt 'Aerialist is not installed, type i to install or just enter to continue test.' [ "$OUT" ] = 'i' && Install fi fi # Show info in test mode if [ $TEST -ne 0 ]; then echo "Aerialist: $gVers ($gDate) Season: $SEASON Movies: $AERIAL Prefs: $PREFS Formats: $FORMATS Lockfile: $LOCKF Player: $PLAYER ($(which $PLAYER 2>/dev/null)) Curl: $(which curl 2>/dev/null) YouTube: $YTDL MediaInfo: $(which mediainfo 2>/dev/null) Process: $$ " fi # Plays the video - local and remote for i in "$PLAYER" 'curl'; do command -v "$i" >/dev/null 2>&1 || Throw "${0##*/} requires $i but it's not installed. Aborting." done } Lock() { local pid # Lock for this display LOCKF="$XDG_RUNTIME_DIR/.${0##*/}.${DISPLAY#*:}.lock" if [ -e "$LOCKF" ]; then pid=$(head -n 1 "$LOCKF") # aerialist IsUp "$pid" && Throw "${0##*/} locked by $pid in $LOCKF" 33 pid=$(tail -n 1 "$LOCKF") # $PLAYER IsUp && Dbg "Ninja: $pid" && kill -9 "$pid" fi echo $$ > "$LOCKF" } LoadWatch() { # Kill if high CPU after some time LoadFnc='Secs() { local s d t; s=0; t="$1"; d="${t%%-*}"; [ "$d" != "$t" ] && s=$((24 * 3600 * ${d#0})) && t="${t#*-}"; d="${t##*:}"; t="${t%:*}"; s=$(($s + ${d#0})); [ "$d" != "$t" ] && { d="${t##*:}"; t="${t%:*}"; s=$(($s + 60 * ${d#0})); }; [ "$d" != "$t" ] && { s=$(($s + 3600 * ${t#0})); }; eval "$2"="$s"; }; LoadPct() { OUT=$(ps -o etime,time -p "$1" | { while read e t; do [ "$e" = "${e#E}" ] && { Secs "$e" E; Secs "$t" T; echo $(($T * 100 / $E)); }; done; }) || OUT=-1; }' sh -c "sleep 66; $LoadFnc; LoadPct $1; [ \$OUT -gt 50 ] && { echo Overloaded:\$OUT; kill $1; } || echo LoadOK:\$OUT" >> "$LOGF" 2>&1 & } Play() { local err # Prep Subtitles "$CTITLE" > "$SUBTITLE" # Create SUBTITLE blurp at 0,5,10,15 minutes e.g. PlayerArgs "$SUBTITLE"; args="$OUT" STARTAT=$(date +%s) # Test play if [ $TEST -ne 0 ]; then [ "$CHOICE" = "${CHOICE#$ONLINETPT:}" ] && ls -lh "$CHOICE" Prompt " ------------------------------------ Test command: $PLAYER $args \"$CHOICE\" Choice: $CHOICE ID: $CIDENT Format: $CFORM (if blank, not read because FORMATS not set) Title: $CTITLE " [ "$OUT" = 't' ] && Msg 'Subtitle sample' && head -n 10 "$SUBTITLE" LoadWatch $$ # Watch ourselves to test "$PLAYER" $args "$CHOICE" err=$? MPID=-9 Stop "Test mode ended (return from player was $err)" # Online play elif [ "${CHOICE}" != "${CHOICE#$ONLINETPT:}" ]; then if [ $ONLINE -ne 0 ]; then "$PLAYER" $args "$CHOICE" >> "$LOGF" 2>&1 & MPID=$! Log "Stream ($MPID/$CFORM): $PLAYER $args \"$CHOICE\"" else Throw "Online play disabled for $CHOICE" fi # Cache play elif [ -s "$CHOICE" ]; then "$PLAYER" $args "$CHOICE" >> "$LOGF" 2>&1 & MPID=$! Log "Cache Play ($MPID/$CFORM): $PLAYER $args \"$CHOICE\"" else Throw "*** $MOVIES ($CHOICE) is uncached and ONLINE is $ONLINE ($ONLINE4K/$ONLINEHDR) ***" 99 fi # Wait for player to finish echo "$MPID" >> "$LOCKF" LoadWatch "$MPID" wait $MPID; err=$? # 127 if bad pid, else retval from process Log "Playing ended: $err" sleep 3 # Sometimes PID hangs around after state change [ $err -gt 128 ] && Stop || SetState 0 } Options "$@" Lock # Catch interupts to clean up trap Stop TERM INT HUP # Until we are killed by a signal while [ 9 -ne 11 ]; do ChooseVideo "$oSeason" Play done ################################################################################################################# # * Notes # Video descriptions incomplete - could not find a table of them online # (See VideoDescription function below) # # Todo # 003 GLK 240223 'standalone' option to be a screensaver without help from xscreensaver # 002 GLK 240216 Store HighCPU format of video in FORMATS to avoid in future # 001 GLK 240131 Seasonal weight column to mix by %age # # History # 029 GLK 240320 Add speed option to adjust playback speed (adjust state accordingly) # 028 GLK 240225 Fix install when no .xscreensaver was present, add InstallMe to be used by curl install, Happy Birthday Alaya! # 027 GLK 240222 Broke get/set state at some point - fixed/optimized, $CFORM, bad cached item removal # 026 GLK 240216 Monitor CPU usage of $PLAYER - if it is over 50% at 1 minute; skip to next, check duration when saving state if able # 025 GLK 240209 Downloads gone crazy; need setting to use but not get new downloads, state did not resume from 0, stateDB uses ID # 024 GLK 240208 Redid core caching / ATV choosing, fix some Darwinisms # 023 GLK 240206 Broke Apple streaming ... will test, Comment in final field of STABLE # 022 GLK 240205 Fix RESUME - was never working # 021 GLK 240202 Seasonal weight support in STABLE (but not yet implemented) # 020 GLK 240131 Move all video IDs to BITES for extension / replacement in settings # 019 GLK 240130 Support Dailymotion via dmo: prefix on video IDs # 018 GLK 240129 Some formats not playing well on e.g. 8559U processors (AV1) - skip them via FORMATS # 017 GLK 240129 Start one video download per season in backgroundnd if folder count < expected count for season # 016 GLK 240129 Added seasons / a few videos for them, started background caching # 015 GLK 240127 Add (prefer) mpv support, start vlc - reason - mplayer using 80% CPU, mpv 15% ... even with -vo vdpau # 014 GLK 240123 Bugs # 013 GLK 231227 Fix darwin UID being ro, prompt to install w/ no parameters if not installed # 012 GLK 231224 Log download beg/end # 011 GLK 231218 Move History here ... makes more sense, settings command # 010 GLK 231217 Install can over-install, create .xscreensaver (untested)., Offer to DL videos at install # 009 GLK 231216 Resume movies where screensaver was interrupted (for long youtube videos) # 008 GLK 231215 Download YT videos into AERIAL/SEASON folder if not populated but requested # 007 GLK 231214 Tear up night/day ... just use seasons, Apple video integration # 006 GLK 231212 Support youtube-dl videos with .webm, .mp4, .mkv, sound option in STABLE # 005 GLK 231211 In support of christmas (Fireplace videos), allow "Aerial" itself to be changed (SEASON) # 004 GLK 231210 panscan parameter per AUR # 003 GLK 231113 Test/fix on NetBSD and 'install' # 002 GLK 231112 Lock (workaround some leftovers detected), ONLINE option # 001 GLK 231105 Partial rewrite # # BSD (bourne/dash) compatible # # Tested: on Kubuntu, Artix, Void, PopOS / Linux, FreeBSD, NetBSD # Requires: mpv (or $PLAYER), awk (install), sed, grep & friends, youtube-dl, curl, mediainfo for FORMATS #################################################################################################################