media scwrypts v5 refactor
This commit is contained in:
parent
a3410d9b15
commit
a20d23ad5e
8
scwrypts/media/.config/env.yaml
Normal file
8
scwrypts/media/.config/env.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
media-sync:
|
||||
.DESCRIPTION: >-
|
||||
s3 bucket name and filesystem targets for media backups
|
||||
s3-bucket:
|
||||
.ENVIRONMENT: MEDIA_SYNC__S3_BUCKET
|
||||
targets:
|
||||
.ENVIRONMENT: MEDIA_SYNC__TARGETS
|
5
scwrypts/media/README.md
Normal file
5
scwrypts/media/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# ZSH Scwrypts
|
||||
[](https://github.com/ytdl-org/youtube-dl)
|
||||
<br>
|
||||
|
||||
Quick wrappers for downloading and trimming YouTube videos.
|
5
scwrypts/media/audio/audio.module.zsh
Normal file
5
scwrypts/media/audio/audio.module.zsh
Normal file
@ -0,0 +1,5 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
#
|
14
scwrypts/media/audio/play-sfx
Executable file
14
scwrypts/media/audio/play-sfx
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env zsh
|
||||
#####################################################################
|
||||
|
||||
use audio/play-sfx --group media
|
||||
|
||||
#####################################################################
|
||||
|
||||
media.audio.play-sfx.parse.usage
|
||||
|
||||
#####################################################################
|
||||
|
||||
MAIN() {
|
||||
media.audio.play-sfx $@
|
||||
}
|
79
scwrypts/media/audio/play-sfx.module.zsh
Normal file
79
scwrypts/media/audio/play-sfx.module.zsh
Normal file
@ -0,0 +1,79 @@
|
||||
#####################################################################
|
||||
|
||||
use notify
|
||||
use scwrypts/get-realpath
|
||||
|
||||
DEPENDENCIES+=(canberra-gtk-play)
|
||||
REQUIRED_ENV+=(DESKTOP__SFX_PATH)
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop)
|
||||
|
||||
eval "$(utils.parse.autosetup)"
|
||||
|
||||
##########################################
|
||||
|
||||
echo.status 'starting playback'
|
||||
canberra-gtk-play -f "${SFX_FILE}" \
|
||||
&& echo.success "finished output of '${SFX_FILE}'" \
|
||||
|| notify.error "something went wrong playing file '${SFX_FILE}'" \
|
||||
|| return 1
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.parse() {
|
||||
[[ ${POSITIONAL_ARGS} -eq 0 ]] || return 0
|
||||
|
||||
((POSITIONAL_ARGS+=1))
|
||||
case $1 in
|
||||
( backlight ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-audio-volume-change.oga" ;;
|
||||
( gamedock ) SFX_FILE="${DESKTOP__SFX_PATH}/gamedock.oga" ;;
|
||||
( homedock ) SFX_FILE="${DESKTOP__SFX_PATH}/homedock.oga" ;;
|
||||
( login ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-desktop-login.oga" ;;
|
||||
( logout ) SFX_FILE="${DESKTOP__SFX_PATH}/smooth-desktop-login.oga" ;;
|
||||
( mute ) SFX_FILE="${DESKTOP__SFX_PATH}/smooth-dialog-warning.oga" ;;
|
||||
( notify ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-complete.oga" ;;
|
||||
( undock ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-desktop-login.oga" ;;
|
||||
( volume ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-message.oga" ;;
|
||||
|
||||
( * )
|
||||
SFX_FILE="$1"
|
||||
;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.locals() {
|
||||
local SFX_FILE
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.usage() {
|
||||
USAGE__description='
|
||||
play the indicated sound effect by mapped name or filename
|
||||
|
||||
mapped names :
|
||||
backlight notify
|
||||
login logout
|
||||
mute volume
|
||||
gamedock homedock undock
|
||||
'
|
||||
|
||||
USAGE__args='
|
||||
\$1 mapped name or filename
|
||||
'
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.validate() {
|
||||
[ -f "$(scwrypts.get-realpath "${SFX_FILE}")" ] \
|
||||
&& SFX_FILE="$(scwrypts.get-realpath "${SFX_FILE}")" \
|
||||
|| SFX_FILE="${DESKTOP__SFX_PATH}/${SFX_FILE}" \
|
||||
;
|
||||
|
||||
[ -f "${SFX_FILE}" ] \
|
||||
|| notify.error "unable to locate sfx file '$1'" \
|
||||
|| return 1
|
||||
}
|
42
scwrypts/media/audio/pulseaudio/pulseaudio.module.zsh
Normal file
42
scwrypts/media/audio/pulseaudio/pulseaudio.module.zsh
Normal file
@ -0,0 +1,42 @@
|
||||
#####################################################################
|
||||
|
||||
DEPENDENCIES+=(canberra-gtk-play)
|
||||
REQUIRED_ENV+=()
|
||||
|
||||
use notify
|
||||
|
||||
#####################################################################
|
||||
|
||||
media.audio.play-sfx() {
|
||||
local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop)
|
||||
local SFX_FILE
|
||||
case $1 in
|
||||
( volume ) SFX_FILE=$DESKTOP__SFX_PATH/yaru-message.oga ;;
|
||||
( mute ) SFX_FILE=$DESKTOP__SFX_PATH/smooth-dialog-warning.oga ;;
|
||||
( backlight ) SFX_FILE=$DESKTOP__SFX_PATH/yaru-audio-volume-change.oga ;;
|
||||
( login ) SFX_FILE=$DESKTOP__SFX_PATH/yaru-desktop-login.oga ;;
|
||||
( logout ) SFX_FILE=$DESKTOP__SFX_PATH/smooth-desktop-login.oga ;;
|
||||
( notify ) SFX_FILE=$DESKTOP__SFX_PATH/yaru-complete.oga ;;
|
||||
( undock ) SFX_FILE=$DESKTOP__SFX_PATH/yaru-desktop-login.oga ;;
|
||||
( homedock ) SFX_FILE=$DESKTOP__SFX_PATH/homedock.oga ;;
|
||||
( gamedock ) SFX_FILE=$DESKTOP__SFX_PATH/gamedock.oga ;;
|
||||
|
||||
* ) SFX_FILE="$1"
|
||||
;;
|
||||
esac
|
||||
|
||||
[ ! -f $SFX_FILE ] && SFX_FILE="$DESKTOP__SFX_PATH/$SFX_FILE"
|
||||
|
||||
[ -f $SFX_FILE ] \
|
||||
&& echo.status "detected file '$SFX_FILE'" \
|
||||
|| notify.error "unable to locate sfx file '$1'" \
|
||||
|| return 1 \
|
||||
;
|
||||
|
||||
echo.status 'starting playback'
|
||||
canberra-gtk-play -f "$SFX_FILE" \
|
||||
&& echo.success "finished output of '$SFX_FILE'" \
|
||||
|| notify.error "something went wrong playing file '$SFX_FILE'" \
|
||||
|| return 1 \
|
||||
;
|
||||
}
|
12
scwrypts/media/audio/pulseaudio/volume
Executable file
12
scwrypts/media/audio/pulseaudio/volume
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/zsh
|
||||
#####################################################################
|
||||
|
||||
use audio/pulseaudio/volume --group media
|
||||
|
||||
#####################################################################
|
||||
|
||||
media.audio.pulseaudio.volume.parse.usage
|
||||
|
||||
#####################################################################
|
||||
|
||||
MAIN() { media.audio.pulseaudio.volume $@; }
|
93
scwrypts/media/audio/pulseaudio/volume.module.zsh
Normal file
93
scwrypts/media/audio/pulseaudio/volume.module.zsh
Normal file
@ -0,0 +1,93 @@
|
||||
#!/bin/zsh
|
||||
#####################################################################
|
||||
|
||||
use notify
|
||||
use audio/play-sfx --group media
|
||||
|
||||
DEPENDENCIES+=(pactl)
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop)
|
||||
eval "$(utils.parse.autosetup)"
|
||||
##########################################
|
||||
|
||||
case ${COMMAND} in
|
||||
( up )
|
||||
pactl set-${DEVICE}-volume ${PACTL_DEVICE} +10% \
|
||||
|| notify.error "pactl error with set-${DEVICE}-volume"
|
||||
|
||||
media.audio.play-sfx volume
|
||||
;;
|
||||
|
||||
( down )
|
||||
pactl set-${DEVICE}-volume ${PACTL_DEVICE} -10% \
|
||||
|| notify.error "pactl error with set-${DEVICE}-volume"
|
||||
|
||||
media.audio.play-sfx volume
|
||||
;;
|
||||
|
||||
( mute )
|
||||
pactl set-${DEVICE}-mute ${PACTL_DEVICE} toggle \
|
||||
&& notify.success "default ${DEVICE}" "$(amixer sget ${AMIXER_DEVICE} | grep -q '\[on\]' && echo unmuted || echo muted)" \
|
||||
|| notify.error "pactl error with set-${DEVICE}-mute"
|
||||
|
||||
media.audio.play-sfx mute
|
||||
;;
|
||||
esac
|
||||
|
||||
return ${ERRORS}
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.parse() { return 0; }
|
||||
${scwryptsmodule}.parse.locals() {
|
||||
local ARGS=()
|
||||
|
||||
local DEVICE
|
||||
local AMIXER_DEVICE
|
||||
local PACTL_DEVICE
|
||||
local COMMAND
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.usage() {
|
||||
USAGE__description='
|
||||
simplified pactl for volume up/down/mute on default sink and source
|
||||
'
|
||||
|
||||
USAGE__args='
|
||||
\$1 target device : one of (sink source)
|
||||
\$2 volume command : one of (up down mute)
|
||||
'
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.validate() {
|
||||
DEVICE="${ARGS[1]}"
|
||||
[ "${DEVICE}" ] \
|
||||
|| notify.error 'missing device'
|
||||
|
||||
COMMAND="${ARGS[2]}"
|
||||
[ "${COMMAND}" ] \
|
||||
|| notify.error 'missing command'
|
||||
|
||||
[ "${DEVICE}" ] && [ "${COMMAND}" ] || return
|
||||
|
||||
case ${DEVICE} in
|
||||
( sink ) AMIXER_DEVICE=Master ;;
|
||||
( source ) AMIXER_DEVICE=Capture ;;
|
||||
( * )
|
||||
notify.error "unsupported device '${DEVICE}'"
|
||||
;;
|
||||
esac
|
||||
|
||||
case ${COMMAND} in
|
||||
( up | down | mute ) ;;
|
||||
( * )
|
||||
notify.error "unsupported command '${COMMAND}'"
|
||||
;;
|
||||
esac
|
||||
|
||||
PACTL_DEVICE="@DEFAULT_$(echo ${DEVICE} | tr '[:lower:]' '[:upper:]')@"
|
||||
}
|
14
scwrypts/media/cloud/cloud.module.zsh
Normal file
14
scwrypts/media/cloud/cloud.module.zsh
Normal file
@ -0,0 +1,14 @@
|
||||
#
|
||||
# cloud media synchronization tools
|
||||
#
|
||||
|
||||
|
||||
# synchronize cloud media with configured targets
|
||||
use cloud/synchronize --group media
|
||||
|
||||
# synchronize cloud media with a specific target
|
||||
use cloud/synchronize-target --group media
|
||||
|
||||
|
||||
# common parsers
|
||||
use cloud/zshparse --group media
|
58
scwrypts/media/cloud/synchronize
Executable file
58
scwrypts/media/cloud/synchronize
Executable file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env zsh
|
||||
#####################################################################
|
||||
|
||||
use cloud --group media
|
||||
|
||||
#####################################################################
|
||||
|
||||
media.cloud.zshparse.actions.usage
|
||||
|
||||
USAGE__options+='
|
||||
--target <string> local/remote target to synchronize (optional)
|
||||
'
|
||||
|
||||
USAGE__description='
|
||||
synchronize local media with an S3 bucket; *-synchronize actions
|
||||
will perform a pull/push back-to-back in the indicated order
|
||||
'
|
||||
|
||||
#####################################################################
|
||||
|
||||
MAIN() {
|
||||
local \
|
||||
TARGET \
|
||||
ACTION \
|
||||
PARSERS=(
|
||||
media.cloud.zshparse.actions
|
||||
)
|
||||
|
||||
eval "$ZSHPARSEARGS"
|
||||
|
||||
##########################################
|
||||
|
||||
case ${TARGET} in
|
||||
( '' )
|
||||
media.cloud.synchronize --action "${ACTION}"
|
||||
;;
|
||||
( * )
|
||||
media.cloud.synchronize-target --action "${ACTION}" --target "${TARGET}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
MAIN.parse() {
|
||||
# local TARGET
|
||||
local PARSED=0
|
||||
|
||||
case $1 in
|
||||
( --target )
|
||||
PARSED=2
|
||||
TARGET="$2"
|
||||
;;
|
||||
esac
|
||||
|
||||
return $PARSED
|
||||
}
|
||||
|
88
scwrypts/media/cloud/synchronize-target.module.zsh
Normal file
88
scwrypts/media/cloud/synchronize-target.module.zsh
Normal file
@ -0,0 +1,88 @@
|
||||
#####################################################################
|
||||
|
||||
REQUIRED_ENV+=(MEDIA_SYNC__S3_BUCKET)
|
||||
|
||||
use cloud/aws
|
||||
use cloud/zshparse/actions --group media
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
local \
|
||||
TARGET TARGET_CLOUD TARGET_LOCAL \
|
||||
ACTION \
|
||||
PARSERS=(
|
||||
media.cloud.zshparse.actions
|
||||
)
|
||||
|
||||
eval "$ZSHPARSEARGS"
|
||||
|
||||
##########################################
|
||||
|
||||
local A B
|
||||
case $ACTION in
|
||||
( push ) A="$TARGET_LOCAL"; B="$TARGET_CLOUD" ;;
|
||||
( pull ) A="$TARGET_CLOUD"; B="$TARGET_LOCAL" ;;
|
||||
|
||||
( pull-first-synchronize )
|
||||
: \
|
||||
&& media.cloud.synchronize-target \
|
||||
--target "${TARGET}" \
|
||||
--action pull \
|
||||
&& media.cloud.synchronize-target \
|
||||
--target "${TARGET}" \
|
||||
--action push \
|
||||
&& return 0 \
|
||||
|| return 1 \
|
||||
;
|
||||
;;
|
||||
|
||||
( push-first-synchronize )
|
||||
: \
|
||||
&& media.cloud.synchronize-target \
|
||||
--target "${TARGET}" \
|
||||
--action push \
|
||||
&& media.cloud.synchronize-target \
|
||||
--target "${TARGET}" \
|
||||
--action pull \
|
||||
&& return 0 \
|
||||
|| return 1 \
|
||||
;
|
||||
;;
|
||||
esac
|
||||
|
||||
echo.status "${ACTION}ing ${TARGET}"
|
||||
cloud.aws s3 sync $A $B \
|
||||
&& echo.success "${TARGET} up-to-date" \
|
||||
|| { ERROR "unable to sync ${TARGET} (see above)"; return 1; }
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.parse() {
|
||||
# local TARGET TARGET_CLOUD TARGET_LOCAL
|
||||
local PARSED=0
|
||||
|
||||
case $1 in
|
||||
( --target )
|
||||
PARSED=2
|
||||
TARGET="$2"
|
||||
;;
|
||||
esac
|
||||
|
||||
return $PARSED
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.usage() {
|
||||
USAGE__options+="
|
||||
--target <string> local/remote target to synchronize
|
||||
"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.validate() {
|
||||
[ "${TARGET}" ] \
|
||||
|| ERROR 'must specify a target'
|
||||
|
||||
TARGET_LOCAL="${HOME}/${TARGET}"
|
||||
TARGET_CLOUD="s3://${MEDIA_SYNC__S3_BUCKET}/${TARGET}"
|
||||
}
|
43
scwrypts/media/cloud/synchronize.module.zsh
Normal file
43
scwrypts/media/cloud/synchronize.module.zsh
Normal file
@ -0,0 +1,43 @@
|
||||
#####################################################################
|
||||
|
||||
REQUIRED_ENV+=(MEDIA_SYNC__TARGETS)
|
||||
|
||||
use cloud/synchronize-target --group media
|
||||
use cloud/zshparse/actions --group media
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
eval "$(USAGE.reset)"
|
||||
|
||||
local \
|
||||
ACTION \
|
||||
PARSERS=(
|
||||
media.cloud.zshparse.actions
|
||||
)
|
||||
|
||||
eval "$ZSHPARSEARGS"
|
||||
|
||||
##########################################
|
||||
|
||||
local TARGET_COUNT=${#MEDIA_SYNC__TARGETS[@]}
|
||||
local FAILED_COUNT=0
|
||||
|
||||
echo.status "starting media ${ACTION}"
|
||||
|
||||
local TARGET
|
||||
for TARGET in ${MEDIA_SYNC__TARGETS[@]}
|
||||
do
|
||||
media.cloud.synchronize-target \
|
||||
--action "${ACTION}" \
|
||||
--target "${TARGET}" \
|
||||
|| ((FAILED_COUNT+=1))
|
||||
done
|
||||
|
||||
[[ $FAILED_COUNT -eq 0 ]] \
|
||||
&& echo.success "successfully completed ${ACTION} for ${TARGET_COUNT} / ${TARGET_COUNT} target(s)" \
|
||||
|| ERROR "failed ${ACTION} for ${FAILED_COUNT} / ${TARGET_COUNT} target(s)" \
|
||||
;
|
||||
}
|
||||
|
||||
#####################################################################
|
37
scwrypts/media/cloud/zshparse/actions.module.zsh
Normal file
37
scwrypts/media/cloud/zshparse/actions.module.zsh
Normal file
@ -0,0 +1,37 @@
|
||||
${scwryptsmodule}() {
|
||||
# local ACTION
|
||||
local PARSED=0
|
||||
|
||||
case $1 in
|
||||
( --action )
|
||||
PARSED=2
|
||||
ACTION="$2"
|
||||
;;
|
||||
esac
|
||||
|
||||
return $PARSED
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.usage() {
|
||||
USAGE__options+="
|
||||
--action <string> a media sync action:
|
||||
push
|
||||
pull
|
||||
push-first-synchronize
|
||||
pull-first-synchronize
|
||||
"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.validate() {
|
||||
case "${ACTION}" in
|
||||
( push | pull | pull-first-synchronize | push-first-synchronize ) ;;
|
||||
( '' )
|
||||
ERROR 'must specify a media sync action'
|
||||
;;
|
||||
( * )
|
||||
ERROR "invalid media sync action '${ACTION}'"
|
||||
;;
|
||||
esac
|
||||
}
|
5
scwrypts/media/cloud/zshparse/zshparse.module.zsh
Normal file
5
scwrypts/media/cloud/zshparse/zshparse.module.zsh
Normal file
@ -0,0 +1,5 @@
|
||||
#
|
||||
# common parsers for cloud media synchronization
|
||||
#
|
||||
|
||||
use cloud/zshparse/actions --group media
|
9
scwrypts/media/ffmpeg/ffmpeg.module.zsh
Normal file
9
scwrypts/media/ffmpeg/ffmpeg.module.zsh
Normal file
@ -0,0 +1,9 @@
|
||||
#
|
||||
# personal ffmpeg utility since I don't use ffmpeg much and don't
|
||||
# want to read the man every time
|
||||
#
|
||||
|
||||
DEPENDENCIES+=(ffmpeg)
|
||||
|
||||
use ffmpeg/get-audio-clip-from-video.module.zsh --group media
|
||||
use ffmpeg/get-video-length-seconds.module.zsh --group media
|
145
scwrypts/media/ffmpeg/get-audio-clip-from-video.module.zsh
Normal file
145
scwrypts/media/ffmpeg/get-audio-clip-from-video.module.zsh
Normal file
@ -0,0 +1,145 @@
|
||||
#####################################################################
|
||||
|
||||
DEPENDENCIES+=(ffmpeg)
|
||||
|
||||
use ffmpeg/get-video-length-seconds --group media
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
eval "$(USAGE.reset)"
|
||||
local USAGE__description='
|
||||
converts a video into an audio clip (mp3)
|
||||
|
||||
if only --start is used, the audio clip will be from
|
||||
the specified start point to the end of the video
|
||||
|
||||
if only --end is used, the audio clip will be from
|
||||
the start of the video to the specified end point
|
||||
|
||||
if --start, --end, and --use-whole-video are all omitted,
|
||||
start and end times will be prompted interactively
|
||||
'
|
||||
|
||||
local \
|
||||
INPUT_FILENAME OUTPUT_FILENAME \
|
||||
USE_WHOLE_VIDEO=false \
|
||||
START_TIME_SECONDS END_TIME_SECONDS \
|
||||
PARSERS=()
|
||||
|
||||
eval "$ZSHPARSEARGS"
|
||||
|
||||
##########################################
|
||||
|
||||
echo.status "converting video to audio
|
||||
video input : ${INPUT_FILENAME}
|
||||
audio output : ${OUTPUT_FILENAME}
|
||||
start time : ${START_TIME_SECONDS}
|
||||
end time : ${END_TIME_SECONDS}
|
||||
"
|
||||
|
||||
ffmpeg -i "${INPUT_FILENAME}" -q:a 0 -map a \
|
||||
-ss ${START_TIME_SECONDS} -t $((${END_TIME_SECONDS} - ${START_TIME_SECONDS}))\
|
||||
"${OUTPUT_FILENAME}" \
|
||||
&& echo.success "created clip '${OUTPUT_FILENAME}'" \
|
||||
|| ERROR "error creating clip '$(basename -- "${OUTPUT_FILENAME}")' (see above)"
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.parse() {
|
||||
# local INPUT_FILENAME OUTPUT_FILENAME
|
||||
# local USE_WHOLE_VIDEO=false
|
||||
# local START_TIME_SECONDS END_TIME_SECONDS optional
|
||||
|
||||
local PARSED=0
|
||||
|
||||
case $1 in
|
||||
-i | --input-filename ) PARSED=2; INPUT_FILENAME="$2" ;;
|
||||
-o | --output-filename ) PARSED=2; OUTPUT_FILENAME="$2" ;;
|
||||
|
||||
--start ) PARSED=2; START_TIME_SECONDS="$2" ;;
|
||||
--end ) PARSED=2; END_TIME_SECONDS="$2" ;;
|
||||
|
||||
--use-whole-video ) PARSED=1; USE_WHOLE_VIDEO=true ;;
|
||||
esac
|
||||
|
||||
return $PARSED
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.usage() {
|
||||
USAGE__options='
|
||||
-i, --input-filename <string> fully-qualified path to input file
|
||||
-o, --output-filename <string> fully-qualified path to output file
|
||||
|
||||
--start <seconds> start time of the clip
|
||||
--end <seconds> end time of the clip
|
||||
|
||||
--use-whole-video convert whole video instead of just a portion
|
||||
'
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.validate() {
|
||||
: \
|
||||
&& [ "${INPUT_FILENAME}" ] \
|
||||
&& [ -f "${INPUT_FILENAME}" ] \
|
||||
&& [ "${OUTPUT_FILENAME}" ] \
|
||||
|| ERROR "must provide a valid input and output filename\ninput : '${INPUT_FILENAME}\noutput : '${OUTPUT_FILENAME}'" \
|
||||
|| return
|
||||
|
||||
[[ ${OUTPUT_FILENAME} =~ .mp3$ ]] || OUTPUT_FILENAME="${OUTPUT_FILENAME}.mp3"
|
||||
|
||||
local VIDEO_LENGTH_SECONDS=$(media.ffmpeg.get-video-length-seconds "${INPUT_FILENAME}")
|
||||
[ "${VIDEO_LENGTH_SECONDS}" ] && [[ "${VIDEO_LENGTH_SECONDS}" -gt 0 ]] \
|
||||
|| ERROR "unable to determine video length; is '${INPUT_FILENAME}' a video?" \
|
||||
|| return
|
||||
|
||||
local CLIP_METHOD=start-time-to-end-time
|
||||
case ${USE_WHOLE_VIDEO} in
|
||||
true )
|
||||
[ ! "${START_TIME_SECONDS}" ] \
|
||||
|| ERROR "conflicting arguments '--start' and '--use-whole-video'"
|
||||
|
||||
[ ! "${END_TIME_SECONDS}" ] \
|
||||
|| ERROR "conflicting arguments '--end' and '--use-whole-video'"
|
||||
;;
|
||||
false )
|
||||
[ ! "${START_TIME_SECONDS}" ] && [ ! "${END_TIME_SECONDS}" ] \
|
||||
&& CLIP_METHOD=interactive
|
||||
;;
|
||||
esac
|
||||
|
||||
case ${CLIP_METHOD} in
|
||||
start-time-to-end-time )
|
||||
[ "${START_TIME_SECONDS}" ] || START_TIME_SECONDS=0
|
||||
[ "${END_TIME_SECONDS}" ] || END_TIME_SECONDS="${VIDEO_LENGTH_SECONDS}"
|
||||
;;
|
||||
|
||||
interactive )
|
||||
START_TIME_SECONDS=$(echo 0 | utils.fzf.user-input "enter start time (0 ≤ t < ${VIDEO_LENGTH_SECONDS})")
|
||||
[ "${START_TIME_SECONDS}" ] \
|
||||
|| ERROR 'interactive user abort' \
|
||||
|| return
|
||||
|
||||
END_TIME_SECONDS=$(echo ${VIDEO_LENGTH_SECONDS} | utils.fzf.user-input "enter end time (${START_TIME_SECONDS} > t ≥ $VIDEO_LENGTH_SECONDS)")
|
||||
[ "${END_TIME_SECONDS}" ] \
|
||||
|| ERROR 'interactive user abort' \
|
||||
|| return
|
||||
;;
|
||||
esac
|
||||
|
||||
[[ "${START_TIME_SECONDS}" -ge 0 ]] \
|
||||
|| ERROR "cannot use negative start time (start time = ${START_TIME_SECONDS})"
|
||||
|
||||
[[ "${END_TIME_SECONDS}" -gt 0 ]] \
|
||||
|| ERROR "end time must be after the video starts (end time = ${END_TIME_SECONDS})"
|
||||
|
||||
[[ "${START_TIME_SECONDS}" -lt "${VIDEO_LENGTH_SECONDS}" ]] \
|
||||
|| ERROR "start time must be before video ends (start time = ${START_TIME_SECONDS}; video length = ${VIDEO_LENGTH_SECONDS})"
|
||||
|
||||
[[ "${END_TIME_SECONDS}" -le "${VIDEO_LENGTH_SECONDS}" ]] \
|
||||
|| ERROR "end time cannot go beyond video end (end time = ${END_TIME_SECONDS}; video length = ${VIDEO_LENGTH_SECONDS})"
|
||||
|
||||
[[ "${START_TIME_SECONDS}" -lt "${END_TIME_SECONDS}" ]] \
|
||||
|| ERROR "start time must come before end time (start time = ${START_TIME_SECONDS}; end time = ${END_TIME_SECONDS})"
|
||||
}
|
20
scwrypts/media/ffmpeg/get-video-length-seconds.module.zsh
Normal file
20
scwrypts/media/ffmpeg/get-video-length-seconds.module.zsh
Normal file
@ -0,0 +1,20 @@
|
||||
#####################################################################
|
||||
|
||||
DEPENDENCIES+=(ffprobe)
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
local FILENAME="$1"
|
||||
|
||||
[ "${FILENAME}" ] && [ -f "${FILENAME}" ] \
|
||||
|| ERROR "invalid or missing file '${FILENAME}'" \
|
||||
|| return 1
|
||||
|
||||
ffprobe \
|
||||
-v quiet \
|
||||
-show_entries format=duration \
|
||||
-of default=noprint_wrappers=1:nokey=1 \
|
||||
-i "${FILENAME}" \
|
||||
;
|
||||
}
|
2
scwrypts/media/media.scwrypts.zsh
Normal file
2
scwrypts/media/media.scwrypts.zsh
Normal file
@ -0,0 +1,2 @@
|
||||
readonly ${scwryptsgroup}__type=zsh
|
||||
readonly ${scwryptsgroup}__color=$(utils.colors.magenta)
|
39
scwrypts/media/youtube/download
Executable file
39
scwrypts/media/youtube/download
Executable file
@ -0,0 +1,39 @@
|
||||
#!/bin/zsh
|
||||
#####################################################################
|
||||
|
||||
use youtube --group media
|
||||
|
||||
#####################################################################
|
||||
|
||||
USAGE__description="
|
||||
download videos from youtube
|
||||
"
|
||||
|
||||
USAGE__args='
|
||||
$@ any number of URLS to download (becomes interactive if omitted)
|
||||
'
|
||||
|
||||
#####################################################################
|
||||
|
||||
MAIN() {
|
||||
local URLS=($@)
|
||||
local ARGS=()
|
||||
|
||||
local DOWNLOAD_ERRORS=0
|
||||
|
||||
[[ $# -eq 0 ]] && {
|
||||
URLS=($(echo '' | utils.fzf.user-input 'download URL'))
|
||||
[[ ${#URLS[@]} -gt 0 ]] || ABORT
|
||||
|
||||
ARGS+=(--interactive)
|
||||
}
|
||||
|
||||
local URL FILENAME
|
||||
for URL in ${URLS[@]}
|
||||
do
|
||||
media.youtube.download ${ARGS[@]} --url "${URL}" \
|
||||
|| ((DOWNLOAD_ERRORS+=1))
|
||||
done
|
||||
|
||||
return ${DOWNLOAD_ERRORS}
|
||||
}
|
63
scwrypts/media/youtube/download.module.zsh
Normal file
63
scwrypts/media/youtube/download.module.zsh
Normal file
@ -0,0 +1,63 @@
|
||||
#####################################################################
|
||||
|
||||
use youtube/yt-dlp --group media
|
||||
use youtube/get-filename --group media
|
||||
use youtube/get-download-path --group media
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
eval "$(USAGE.reset)"
|
||||
|
||||
local \
|
||||
URL INTERACTIVE=false \
|
||||
PARSERS=()
|
||||
|
||||
eval "$ZSHPARSEARGS"
|
||||
|
||||
##########################################
|
||||
|
||||
local FILENAME="$(media.youtube.get-filename "${URL}")"
|
||||
|
||||
[ "${FILENAME}" ] \
|
||||
|| ERROR "could not find metadata; cannot proceed with download\n${URL}" \
|
||||
|| return 1
|
||||
|
||||
media.youtube.yt-dlp "${URL}" \
|
||||
--format 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4' \
|
||||
&& echo.success "finished download of ${URL}\n$(media.youtube.get-download-path)/${FILENAME}" \
|
||||
|| ERROR "failed to download '${FILENAME}' (${URL})"
|
||||
;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}.parse() {
|
||||
# local URL INTERACTIVE=false
|
||||
local PARSED=0
|
||||
|
||||
case $1 in
|
||||
--url )
|
||||
PARSED=2
|
||||
URL="$2"
|
||||
;;
|
||||
|
||||
--interactive )
|
||||
PARSED=1
|
||||
INTERACTIVE=true
|
||||
;;
|
||||
esac
|
||||
|
||||
return $PARSED
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.usage() {
|
||||
USAGE__options+="
|
||||
--url <string> the URL of the target video
|
||||
"
|
||||
}
|
||||
|
||||
${scwryptsmodule}.parse.validate() {
|
||||
[ "$URL" ] \
|
||||
|| ERROR "must provide download URL"
|
||||
}
|
38
scwrypts/media/youtube/get-audio-clip
Executable file
38
scwrypts/media/youtube/get-audio-clip
Executable file
@ -0,0 +1,38 @@
|
||||
#!/bin/zsh
|
||||
|
||||
use ffmpeg/get-audio-clip-from-video --group media
|
||||
use youtube/get-download-path --group media
|
||||
|
||||
#####################################################################
|
||||
|
||||
|
||||
#####################################################################
|
||||
|
||||
MAIN() {
|
||||
local DOWNLOAD_PATH="$(media.youtube.get-download-path)"
|
||||
|
||||
local INPUT_FILENAME="$(
|
||||
cd -- "${DOWNLOAD_PATH}"
|
||||
find . -type f -name \*.mp4 \
|
||||
| sed 's|^./||' \
|
||||
| utils.fzf 'select a video' \
|
||||
| sed 's/\.mp4$//' \
|
||||
)"
|
||||
[ "${INPUT_FILENAME}" ] || ABORT
|
||||
|
||||
local OUTPUT_FILENAME="$(\
|
||||
basename -- "${INPUT_FILENAME}" \
|
||||
| sed 's/\.[^.]*$//' \
|
||||
| utils.fzf.user-input 'what should I call this clip? (.mp3)' \
|
||||
| sed 's/\(\.mp3\)*$//' \
|
||||
)"
|
||||
[ "${OUTPUT_FILENAME}" ] || ABORT
|
||||
|
||||
INPUT_FILENAME="${DOWNLOAD_PATH}/${INPUT_FILENAME}.mp4"
|
||||
OUTPUT_FILENAME="${DOWNLOAD_PATH}/${OUTPUT_FILENAME}.mp3"
|
||||
|
||||
media.ffmpeg.get-audio-clip-from-video \
|
||||
--input-filename "${INPUT_FILENAME}" \
|
||||
--output-filename "${OUTPUT_FILENAME}" \
|
||||
;
|
||||
}
|
7
scwrypts/media/youtube/get-download-path.module.zsh
Normal file
7
scwrypts/media/youtube/get-download-path.module.zsh
Normal file
@ -0,0 +1,7 @@
|
||||
${scwryptsmodule}() {
|
||||
local DOWNLOAD_PATH="${SCWRYPTS_DATA_PATH}/youtube"
|
||||
|
||||
mkdir -p -- "${DOWNLOAD_PATH}" &>/dev/null
|
||||
|
||||
echo "${DOWNLOAD_PATH}"
|
||||
}
|
12
scwrypts/media/youtube/get-filename.module.zsh
Normal file
12
scwrypts/media/youtube/get-filename.module.zsh
Normal file
@ -0,0 +1,12 @@
|
||||
#####################################################################
|
||||
|
||||
use youtube/yt-dlp --group media
|
||||
|
||||
#####################################################################
|
||||
|
||||
${scwryptsmodule}() {
|
||||
media.youtube.yt-dlp --dump-json $@ \
|
||||
| jq -r '._filename' \
|
||||
| sed 's/\.[^.]*$/\.mp4/' \
|
||||
;
|
||||
}
|
15
scwrypts/media/youtube/youtube.module.zsh
Normal file
15
scwrypts/media/youtube/youtube.module.zsh
Normal file
@ -0,0 +1,15 @@
|
||||
#
|
||||
# interact with youtube
|
||||
#
|
||||
|
||||
|
||||
# download a youtube video by URL
|
||||
use youtube/download --group media
|
||||
|
||||
|
||||
# show fully-qualified path to downloads
|
||||
use youtube/get-download-path --group media
|
||||
|
||||
|
||||
# interact with yt-dlp directly
|
||||
use youtube/yt-dlp --group media
|
15
scwrypts/media/youtube/yt-dlp.module.zsh
Normal file
15
scwrypts/media/youtube/yt-dlp.module.zsh
Normal file
@ -0,0 +1,15 @@
|
||||
#####################################################################
|
||||
|
||||
use youtube/get-download-path --group media
|
||||
|
||||
#####################################################################
|
||||
|
||||
DEPENDENCIES+=(yt-dlp)
|
||||
${scwryptsmodule}() {
|
||||
(
|
||||
cd -- "$(media.youtube.get-download-path)"
|
||||
yt-dlp \
|
||||
--restrict-filenames \
|
||||
$@
|
||||
)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user