diff options
-rwxr-xr-x | contrib/scripts/chapters-to-tracks.sh | 91 | ||||
-rwxr-xr-x | contrib/scripts/playlist-to-tracks.sh | 88 | ||||
-rwxr-xr-x | contrib/scripts/tracks-to-cues.sh | 176 | ||||
-rwxr-xr-x | contrib/scripts/yt-chapters-to-cdda.sh | 143 |
4 files changed, 355 insertions, 143 deletions
diff --git a/contrib/scripts/chapters-to-tracks.sh b/contrib/scripts/chapters-to-tracks.sh new file mode 100755 index 000000000..f1b84d0f2 --- /dev/null +++ b/contrib/scripts/chapters-to-tracks.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022-2022 kcgen <kcgen@users.noreply.github.com> + +# Bash strict-mode +set -euo pipefail +IFS=$'\n\t' + +print_usage() { + local script="${0##*/}" + echo "" + echo "usage: $script URI [URI [...]]" + echo + echo "Convert a YouTube video with chapters into CD-DA tracks." + echo " - Multiple videos can be provided; each will be converted." + echo " - URIs can be the full https:// URL or just the identifier." + echo "" + echo "Depends on:" + echo " - yt-dlp (install with pip3 install yt-dlp)" + echo " - ffmpeg, and GNU find and sort: install with package manager" + echo "" +} + +main() { + case ${1:--help} in + -h | -help | --help) print_usage ;; + *) + check-dependencies + convert-chapters-to-tracks "$@" + ;; + esac +} + +check-dependencies() { + local missing_deps=0 + for dep in yt-dlp ffmpeg find sort rm; do + if ! command -v "$dep" &>/dev/null; then + echo "Missing dependency: $dep could not be found in the PATH" + ((missing_deps++)) + fi + done + # Were any missing? + if [[ $missing_deps -gt 0 ]]; then + echo "Install the above programs and try again" + exit 1 + fi +} + +download-chapters-from-uri() { + # YouTube Opus format identifiers in order of quality + local webm_formats="338/251/250/249" + + # Directory/##-Track output layout + local cdda_ouput="chapter:%(title)s/%(section_number)02d-%(section_title)s.%(ext)s" + + # Fetch the track and split it on chapters + yt-dlp \ + --split-chapters \ + --restrict-filenames \ + --format "$webm_formats" \ + --output "$cdda_ouput" \ + "$uri" || true +} + +extract-opus-from-webm() { + local opus="${webm%.webm}.opus" + + ffmpeg \ + -hide_banner \ + -loglevel error \ + -i "$webm" \ + -vn -acodec copy "$opus" + + # Delete the WebM source if we've got the Opus + if [[ -f $opus ]]; then + rm "$webm" + fi +} + +convert-chapters-to-tracks() { + for uri in "$@"; do + download-chapters-from-uri + done + for webm in $(find . -maxdepth 2 -mindepth 2 -name '*.webm' | sort || true); do + extract-opus-from-webm + done +} + +main "$@" diff --git a/contrib/scripts/playlist-to-tracks.sh b/contrib/scripts/playlist-to-tracks.sh new file mode 100755 index 000000000..48b71586a --- /dev/null +++ b/contrib/scripts/playlist-to-tracks.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash + +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022-2022 kcgen <kcgen@users.noreply.github.com> + +# Bash strict-mode +set -euo pipefail +IFS=$'\n\t' + +print_usage() { + local script="${0##*/}" + echo "" + echo "usage: $script URI [URI [...]]" + echo + echo "Convert a YouTube playlist into CD-DA track format." + echo " - Multiple playlists can be provided; each will be converted." + echo " - URIs can be the full https:// URL or just the identifier." + echo "" + echo "Depends on:" + echo " - yt-dlp (install with pip3 install yt-dlp)" + echo " - ffmpeg: install with package manager" + echo "" +} + +main() { + case ${1:--help} in + -h | -help | --help) print_usage ;; + *) + check-dependencies + convert-playlists-to-tracks "$@" + ;; + esac +} + +check-dependencies() { + local missing_deps=0 + for dep in yt-dlp ffmpeg find sort rm; do + if ! command -v "$dep" &>/dev/null; then + echo "Missing dependency: $dep could not be found in the PATH" + ((missing_deps++)) + fi + done + # Were any missing? + if [[ $missing_deps -gt 0 ]]; then + echo "Install the above programs and try again" + exit 1 + fi +} + +download-playlist-from-uri() { + # YouTube Opus format identifiers in order of quality + local webm_formats="338/251/250/249" + + # Playlist/##-Track output layout + local cdda_ouput="%(playlist|)s/%(playlist_index)03d-%(title)s.%(ext)s" + + yt-dlp \ + --restrict-filenames \ + --format "$webm_formats" \ + --output "$cdda_ouput" \ + "$uri" || true +} + +extract-opus-from-webm() { + local opus="${webm%.webm}.opus" + + ffmpeg \ + -hide_banner \ + -loglevel error \ + -i "$webm" \ + -vn -acodec copy "$opus" + # Delete the WebM source if we've got the Opus + if [[ -f $opus ]]; then + rm "$webm" + fi +} + +convert-playlists-to-tracks() { + for uri in "$@"; do + download-playlist-from-uri + done + for webm in $(find . -maxdepth 2 -mindepth 2 -name '*.webm' | sort || true); do + extract-opus-from-webm + done +} + +main "$@" diff --git a/contrib/scripts/tracks-to-cues.sh b/contrib/scripts/tracks-to-cues.sh new file mode 100755 index 000000000..1cc0d1cc3 --- /dev/null +++ b/contrib/scripts/tracks-to-cues.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash + +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2022-2022 kcgen <kcgen@users.noreply.github.com> + +# Bash strict-mode +set -euo pipefail +IFS=$'\n\t' + +print_usage() { + readonly SCRIPT="${0##*/}" + echo "" + echo "usage: $SCRIPT DIR [DIR [...]]" + echo + echo "Generate DOS Redbook CD-DA CUE file(s) in the given" + echo "directories made up of flac, wav, opus, ogg, and mp3s" + echo "Track numbers starts at 2 if an ISO file is present." + echo "Redbook's 99 minute/track limits are handled." + echo "" + echo "Depends on: ffprobe (part of ffmpeg), GNU find and sort." + echo "" +} + +main() { + case ${1:--help} in + -h | -help | --help) print_usage ;; + *) + check-dependencies + generate-cues-for-dirs "$@" + ;; + esac +} + +check-dependencies() { + local missing_deps=0 + for dep in ffprobe find sort rm; do + if ! command -v "$dep" &>/dev/null; then + echo "Missing dependency: $dep could not be found in the PATH" + missing_deps=$((missing_deps + 1)) + fi + done + # Were any missing? + if ((missing_deps > 0)); then + echo "Install the above programs and try again" + exit 1 + fi +} + +initialize-state-variables() { + iso="" + + track="" + track_number=1 + track_runtime=0 + track_is_first=1 + readonly track_limit=99 + + cue="cdrom.cue" + cue_number=1 + cue_runtime=0 + readonly runtime_limit=$((99 * 60)) +} + +generate-cues-for-dirs() { + for dir in "$@"; do + { + cd "$dir" + generate-cues-for-tracks + } + done +} + +generate-cues-for-tracks() { + initialize-state-variables + iso=$(get-first-iso) + if [[ -n $iso ]]; then + add-iso-to-cue + fi + for track in $(get-audio-tracks); do + add-track-to-cue + done +} + +add-iso-to-cue() { + { + echo "FILE \"$iso\" BINARY" + echo " TRACK 1 MODE1/2048" + echo " INDEX 01 00:00:00" + echo "" + } >>"$cue" + increment-track-number +} + +add-track-to-cue() { + get-track-runtime + if ((track_runtime > runtime_limit)); then + echo "Skipping $track: $track_runtime s runtime exceeds CD-DAs maximum of $runtime_limit" + return + fi + add-track-runtime-to-cue + { + track_type=${track##*.} + echo "FILE \"$track\" ${track_type^^}" + echo " TRACK $track_number AUDIO" + + if ((track_is_first == 1)); then + echo " PREGAP 00:02:00" + track_is_first=0 + fi + echo " INDEX 01 00:00:00" + echo "" + } >>"$cue" + + increment-track-number +} + +increment-track-number() { + track_number=$((track_number + 1)) + if ((track_number > track_limit)); then + increment-cue + fi +} + +get-track-runtime() { + track_runtime=$(ffprobe \ + -v error \ + -show_entries format=duration \ + -of default=noprint_wrappers=1:nokey=1 \ + "$track") + + # Ceil decimal portion by a full second + track_runtime=$((${track_runtime%%.*} + 1)) +} + +add-track-runtime-to-cue() { + cue_runtime=$((cue_runtime + track_runtime)) + if ((cue_runtime > runtime_limit)); then + increment-cue + fi +} + +increment-cue() { + if ((cue_number == 1)); then + mv "$cue" "cdrom1.cue" + fi + cue_runtime=$track_runtime + cue_number=$((cue_number + 1)) + cue="cdrom${cue_number}.cue" + + track_number=1 + track_is_first=1 +} + +get-first-iso() { + find ./ -type f \ + \( -iname \*.iso \) \ + -printf '%P\n' | + sort --ignore-case | + head -1 || + true +} + +get-audio-tracks() { + find ./ -type f \ + \( -iname \*.flac \ + -o -iname \*.wav \ + -o -iname \*.opus \ + -o -iname \*.ogg \ + -o -iname \*.mp3 \) \ + -printf '%P\n' | + sort --ignore-case || + true +} + +main "$@" diff --git a/contrib/scripts/yt-chapters-to-cdda.sh b/contrib/scripts/yt-chapters-to-cdda.sh deleted file mode 100755 index 7d4cbc9b9..000000000 --- a/contrib/scripts/yt-chapters-to-cdda.sh +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env bash - -# SPDX-License-Identifier: GPL-2.0-or-later -# -# Copyright (C) 2022-2022 kcgen <kcgen@users.noreply.github.com> - -# Bash strict-mode -set -euo pipefail -IFS=$'\n\t' - -SCRIPT=$(basename "$0") -readonly SCRIPT - -print_usage() { - echo "" - echo "usage: $SCRIPT URI [URI [...]]" - echo - echo "Convert a YouTube video with chapters into CDDA+CUE format." - echo " - Multiple videos can be provided; each will be converted." - echo " - URIs can be the full https:// URL or just the identifier." - echo "" - echo "Depends on:" - echo " - yt-dlp (install with pip3 install yt-dlp)" - echo " - ffmpeg: install with package manager" - echo " - basename: install with package manager" - echo "" -} - -main() { - case ${1:-} in - -h | -help | --help) print_usage ;; - *) - check-dependencies - convert-chapters-to-cdda "$@" - ;; - esac -} - -check-dependencies() { - local missing_deps=0 - for dep in yt-dlp ffmpeg find sort rm basename; do - if ! command -v "$dep" &>/dev/null; then - echo "Missing dependency: $dep could not be found in the PATH" - ((missing_deps++)) - fi - done - # Were any missing? - if [[ "$missing_deps" -gt 0 ]]; then - echo "Install the above programs and try again" - exit 1 - fi -} - -download-chapters-from-uri() { - local uri="$1" - - # YouTube Opus format identifiers in order of quality - local webm_formats="338/251/250/249" - - # Directory/##-Track output layout - local cdda_ouput="chapter:%(title)s/%(section_number)02d-%(section_title)s.%(ext)s" - - # Fetch the track and split it on chapters - yt-dlp \ - --split-chapters \ - --restrict-filenames \ - --format "$webm_formats" \ - --output "$cdda_ouput" \ - "$uri" -} - -extract-opus-from-webm() { - local webm="$1" - local opus="${1%.webm}.opus" - - if [[ -f "$webm" && ! -f "$opus" ]]; then - # Extract the Opus stream from the WebM - ffmpeg -hide_banner -loglevel error \ - -i "$webm" \ - -vn -acodec copy "$opus" - # Delete the WebM source if we've got the Opus - if [[ -f "$opus" ]]; then - rm "$webm" - fi - fi -} - -add-track-to-cue() { - local dir - local webm - local opus - dir=$(basename "${1%/*}") - webm=$(basename "$1") - opus=$(basename "$webm" webm)opus - - local track_number="$2" - - # Ensure this directory and the track exists - if [[ ! -d "$dir" || ! -f "$dir/$opus" ]]; then - echo "Problem finding the CDDA directory ($dir) and/or track ($dir/$opus)" - exit 1 - fi - - # Add a CUE entry for the track - cue_path="$dir/cdrom.cue" - { - echo "FILE \"$opus\" OPUS" - echo " TRACK $track_number AUDIO" - # Add pre-gap just for the first track - if [[ "$track_number" == "1" ]]; then - echo " PREGAP 00:02:00" - fi - echo " INDEX 01 00:00:00" - echo "" - } >>"$cue_path" -} - -convert-local-webm-to-cdda() { - local count=1 - for webm in $(find . -maxdepth 2 -mindepth 2 -name '*.webm' | sort); do - extract-opus-from-webm "$webm" - add-track-to-cue "$webm" "$count" - ((count += 1)) - done -} - -print-dosbox-imgmount-line() { - local cue="$1" - if [[ -n "$cue" ]]; then - echo "imgmount d \"$cue\" -t cdrom" - fi -} - -convert-chapters-to-cdda() { - for uri in "$@"; do - cue_path="" - download-chapters-from-uri "$uri" - convert-local-webm-to-cdda - print-dosbox-imgmount-line "$cue_path" - done -} - -main "$@" |