mirror of
https://github.com/foo-dogsquared/dotfiles.git
synced 2025-02-07 06:18:59 +00:00
Refactor the custom scripts
A lot of them are reaching to be bigger so I've refactored them similarly to C codebases with the `main()` entrypoint. Apparently, this is how bigger shell scripts are written like Neofetch, pfetch, and some Kubernetes helper scripts.
This commit is contained in:
parent
1d6065d308
commit
f96888447d
@ -38,8 +38,7 @@ HELP
|
|||||||
|
|
||||||
# Simply prints the given string into percent-encoded equivalent.
|
# Simply prints the given string into percent-encoded equivalent.
|
||||||
#
|
#
|
||||||
# > urlencode "Hello world"
|
# `urlencode "Hello world"` will give "Hello%20world"
|
||||||
# Hello%20world
|
|
||||||
#
|
#
|
||||||
# Stolen from https://gist.github.com/cdown/1163649 and https://gist.github.com/cdown/1163649#gistcomment-1256298.
|
# Stolen from https://gist.github.com/cdown/1163649 and https://gist.github.com/cdown/1163649#gistcomment-1256298.
|
||||||
# Just ported it in Oil script.
|
# Just ported it in Oil script.
|
||||||
|
@ -23,11 +23,16 @@ var channel_id_eggex = / 'https://www.youtube.com/channel/' (word) /
|
|||||||
|
|
||||||
var NEWPIPE_DB = "newpipe.db"
|
var NEWPIPE_DB = "newpipe.db"
|
||||||
var TEMP_FOLDER_NAME = "newpipe"
|
var TEMP_FOLDER_NAME = "newpipe"
|
||||||
var NEWPIPE_DB_QUERY = "SELECT name, url, service_id, group_concat(tag, ',') AS tags FROM (SELECT subscriptions.name, subscriptions.url, subscriptions.service_id, '/' || feed_group.name AS tag
|
|
||||||
FROM subscriptions
|
# The SQL query to get the required metadata.
|
||||||
LEFT JOIN feed_group_subscription_join AS subs_join
|
var NEWPIPE_DB_QUERY = "
|
||||||
LEFT JOIN feed_group
|
SELECT name, url, service_id, group_concat(tag, ',') AS tags FROM (
|
||||||
ON subs_join.subscription_id = subscriptions.uid AND feed_group.uid = subs_join.group_id) GROUP BY name ORDER BY name COLLATE NOCASE;"
|
SELECT subscriptions.name, subscriptions.url, subscriptions.service_id, '/' || feed_group.name AS tag
|
||||||
|
FROM subscriptions
|
||||||
|
LEFT JOIN feed_group_subscription_join AS subs_join
|
||||||
|
LEFT JOIN feed_group
|
||||||
|
ON subs_join.subscription_id = subscriptions.uid AND feed_group.uid = subs_join.group_id
|
||||||
|
) GROUP BY name ORDER BY name COLLATE NOCASE;"
|
||||||
|
|
||||||
# Print the beginning of the template.
|
# Print the beginning of the template.
|
||||||
cat <<OPML
|
cat <<OPML
|
||||||
@ -56,13 +61,24 @@ proc print-outline(title, xml_url, html_url, tags = "") {
|
|||||||
# Print the channels in the OPML body.
|
# Print the channels in the OPML body.
|
||||||
# This only occurs if the given file does have a Newpipe database.
|
# This only occurs if the given file does have a Newpipe database.
|
||||||
if unzip -l $FILENAME | rg --quiet $NEWPIPE_DB {
|
if unzip -l $FILENAME | rg --quiet $NEWPIPE_DB {
|
||||||
|
# Make the temporary directory (preferably on the current directory to make cleanup easier).
|
||||||
mkdir $TEMP_FOLDER_NAME && unzip -q -u $FILENAME -d $TEMP_FOLDER_NAME
|
mkdir $TEMP_FOLDER_NAME && unzip -q -u $FILENAME -d $TEMP_FOLDER_NAME
|
||||||
file --mime "${TEMP_FOLDER_NAME}/${NEWPIPE_DB}" | rg --quiet "application/x-sqlite3" || exit 1
|
|
||||||
|
# Setting up some automatic cleanup upon exit.
|
||||||
trap "rm --recursive $TEMP_FOLDER_NAME" EXIT
|
trap "rm --recursive $TEMP_FOLDER_NAME" EXIT
|
||||||
|
|
||||||
|
# Quickly check if a SQLite database is in the Newpipe database folder.
|
||||||
|
file --mime "${TEMP_FOLDER_NAME}/${NEWPIPE_DB}" | rg --quiet "application/x-sqlite3" || exit 1
|
||||||
|
|
||||||
|
# Extract the data from the database and process them individually.
|
||||||
|
# Note that we formatted the data in CSV to be in one line per object since as of version 0.8.11, Oil has some problems when taking fully nested data from external commands (not yet completely verified).
|
||||||
|
# We have to rewrite this part once Oil fixes the issue with nested objects.
|
||||||
sqlite3 "${TEMP_FOLDER_NAME}/${NEWPIPE_DB}" "${NEWPIPE_DB_QUERY}" --csv --header \
|
sqlite3 "${TEMP_FOLDER_NAME}/${NEWPIPE_DB}" "${NEWPIPE_DB_QUERY}" --csv --header \
|
||||||
| dasel select --parser csv --multiple --selector '.[*]' --compact --write json \
|
| dasel select --parser csv --multiple --selector '.[*]' --compact --write json \
|
||||||
| while read channel {
|
| while read channel {
|
||||||
|
# We have separate each channel as a JSON object per line.
|
||||||
echo $channel | json read :channel
|
echo $channel | json read :channel
|
||||||
|
|
||||||
setvar name = channel['name']
|
setvar name = channel['name']
|
||||||
setvar url = channel['url']
|
setvar url = channel['url']
|
||||||
setvar service_id = channel['service_id']
|
setvar service_id = channel['service_id']
|
||||||
|
160
bin/split-album
160
bin/split-album
@ -1,5 +1,5 @@
|
|||||||
#! /usr/bin/env nix-shell
|
#! /usr/bin/env nix-shell
|
||||||
#! nix-shell -i oil -p coreutils moreutils ffmpeg gnused ripgrep
|
#! nix-shell -i oil -p coreutils moreutils ffmpeg gnused ripgrep file
|
||||||
|
|
||||||
shopt --set strict:all
|
shopt --set strict:all
|
||||||
|
|
||||||
@ -85,20 +85,44 @@ There will be a folder created with the safe name of the album (in kebab-case) c
|
|||||||
The original file will be kept, do what you want with it.
|
The original file will be kept, do what you want with it.
|
||||||
"
|
"
|
||||||
|
|
||||||
const EXTENSION = ${EXTENSION:-"opus"}
|
|
||||||
|
|
||||||
var audio_file = ''
|
proc warnf(msg, @args) {
|
||||||
var timestamp_file = ''
|
>&2 printf "${msg}\\n" @args
|
||||||
|
}
|
||||||
|
|
||||||
var album = ''
|
proc errorf(msg, @args) {
|
||||||
var author = ''
|
>&2 printf "${msg}\\n" @args
|
||||||
var pub_date = ''
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
var prints_json = false
|
proc prompt(msg, :out, prefix = ">> ") {
|
||||||
var strict_mode = false
|
>&2 printf '%s\n%s' $msg $prefix
|
||||||
var skip = false
|
read --line
|
||||||
|
setref out = $_line
|
||||||
|
}
|
||||||
|
|
||||||
while test $len(ARGV) -gt 0 {
|
proc kebab-case(word) {
|
||||||
|
write -- $word | sed --regexp-extended --expression 's/./\L&/g' --expression 's/\s+/-/g' --expression 's/[^a-z0-9-]//g' --expression 's/^-+|-+$//g' --expression 's/-+/-/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
proc main {
|
||||||
|
# This could be configured by setting the 'EXTENSION' environment variable.
|
||||||
|
const EXTENSION = ${EXTENSION:-"opus"}
|
||||||
|
|
||||||
|
# Set up the variables.
|
||||||
|
var audio_file = ''
|
||||||
|
var timestamp_file = ''
|
||||||
|
|
||||||
|
var album = ''
|
||||||
|
var author = ''
|
||||||
|
var pub_date = ''
|
||||||
|
|
||||||
|
var prints_json = false
|
||||||
|
var strict_mode = false
|
||||||
|
var skip = false
|
||||||
|
|
||||||
|
# Parse the arguments.
|
||||||
|
while test $len(ARGV) -gt 0 {
|
||||||
case $[ARGV[0]] {
|
case $[ARGV[0]] {
|
||||||
-h|--help)
|
-h|--help)
|
||||||
write -- $show_help
|
write -- $show_help
|
||||||
@ -146,60 +170,56 @@ while test $len(ARGV) -gt 0 {
|
|||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proc warnf(msg, @args) {
|
# Check the files if it is valid.
|
||||||
>&2 printf "${msg}\\n" @args
|
test -f $audio_file || errorf '%s is not a regular file' $audio_file
|
||||||
}
|
test -f $timestamp_file || errorf '%s is not a regular file' $timestamp_file
|
||||||
|
|
||||||
proc errorf(msg, @args) {
|
# Prompt for the missing values if not passed from the command line.
|
||||||
>&2 printf "${msg}\\n" @args
|
test $album || prompt "What is the title of the album?" :album
|
||||||
exit 1
|
test $author || prompt "Who is the author of the album?" :author
|
||||||
}
|
test $pub_date || prompt "When is the album published?" :pub_date
|
||||||
|
|
||||||
proc prompt(msg, :out, prefix = ">> ") {
|
# Populate the output data.
|
||||||
>&2 printf '%s\n%s' $msg $prefix
|
# This is going to be used throughout the processing.
|
||||||
read --line
|
# Additionally, the object will be printed when `--json` flag is passed.
|
||||||
setref out = $_line
|
const output_data = {}
|
||||||
}
|
setvar output_data['file'] = $audio_file
|
||||||
|
setvar output_data['chapters'] = []
|
||||||
|
setvar output_data['album'] = $album
|
||||||
|
setvar output_data['author'] = $author
|
||||||
|
setvar output_data['date'] = $pub_date
|
||||||
|
setvar output_data['extension'] = $EXTENSION
|
||||||
|
|
||||||
proc kebab-case(word) {
|
# The following variable stores an eggex, a simplified notation for regular expressions.
|
||||||
write -- $word | sed --regexp-extended --expression 's/./\L&/g' --expression 's/\s+/-/g' --expression 's/[^a-z0-9-]//g' --expression 's/^-+|-+$//g' --expression 's/-+/-/g'
|
# Pretty nice to use especially that literals are quoted and classes are not.
|
||||||
}
|
const timestamp_regex = / %start digit{2,} ':' digit{2} ':' digit{2} <'.' digit+>? %end /
|
||||||
|
|
||||||
test -f $audio_file || errorf '%s is not a regular file' $audio_file
|
# We'll keep track whether the pipeline has encountered an error.
|
||||||
test -f $timestamp_file || errorf '%s is not a regular file' $timestamp_file
|
# If it has, the script will exit throughout various points of the process.
|
||||||
|
var has_error = false
|
||||||
|
|
||||||
test $album || prompt "What is the title of the album?" :album
|
# Deserialize the given input into the chapters data.
|
||||||
test $author || prompt "Who is the author of the album?" :author
|
# This script accept several formats from a JSON file to a plain-text file derived from Luke Smith's 'booksplit' script.
|
||||||
test $pub_date || prompt "When is the album published?" :pub_date
|
case $(file --mime-type --brief $timestamp_file) {
|
||||||
|
|
||||||
const output_data = {}
|
|
||||||
setvar output_data['file'] = $audio_file
|
|
||||||
setvar output_data['chapters'] = []
|
|
||||||
setvar output_data['album'] = $album
|
|
||||||
setvar output_data['author'] = $author
|
|
||||||
setvar output_data['date'] = $pub_date
|
|
||||||
setvar output_data['extension'] = $EXTENSION
|
|
||||||
|
|
||||||
const timestamp_regex = / %start digit{2,} ':' digit{2} ':' digit{2} <'.' digit+>? %end /
|
|
||||||
var has_error = false
|
|
||||||
|
|
||||||
# Deserialize the given input into the chapters data.
|
|
||||||
case $(file --mime-type --brief $timestamp_file) {
|
|
||||||
"application/json")
|
"application/json")
|
||||||
json read :chapters < $timestamp_file
|
json read :chapters < $timestamp_file
|
||||||
setvar output_data['chapters'] = chapters
|
setvar output_data['chapters'] = chapters
|
||||||
;;
|
;;
|
||||||
|
|
||||||
# Also cleans up the timestamp file with comments (i.e., lines starting with '#') and empty lines allowing for more commenting options.
|
# The text file is formatted quite similarly to the required format from the booksplit script.
|
||||||
# I just want to improve the timestamp format a little bit.
|
# I improved some things in the format such as allowing comments (i.e., lines starting with '#') and empty lines allowing for cleaner input.
|
||||||
"text/plain")
|
"text/plain")
|
||||||
sed --regexp-extended --expression '/^\s*$/d' --expression '/^#/d' $timestamp_file | while read --line {
|
sed --regexp-extended --expression '/^\s*$/d' --expression '/^#/d' $timestamp_file | while read --line {
|
||||||
|
# We'll build the chapter data to be added later to the output data.
|
||||||
var chapter = {}
|
var chapter = {}
|
||||||
setvar chapter['title'] = $(write -- $_line | cut -d' ' -f2-)
|
setvar chapter['title'] = $(write -- $_line | cut -d' ' -f2-)
|
||||||
setvar chapter['timestamp'] = $(write -- $_line | cut -d' ' -f1)
|
setvar chapter['timestamp'] = $(write -- $_line | cut -d' ' -f1)
|
||||||
|
|
||||||
|
# Mark the input to be erreneous if the timestamp format is not valid.
|
||||||
|
# This will cause the script to exit in the next part of the process.
|
||||||
|
# We won't be exiting immediately to give all possible errors.
|
||||||
write -- ${chapter['timestamp']} | rg --quiet $timestamp_regex || {
|
write -- ${chapter['timestamp']} | rg --quiet $timestamp_regex || {
|
||||||
warnf "'%s' %s is not a valid timestamp" ${chapter['timestamp']} ${chapter['title']}
|
warnf "'%s' %s is not a valid timestamp" ${chapter['timestamp']} ${chapter['title']}
|
||||||
setvar has_error = true
|
setvar has_error = true
|
||||||
@ -209,20 +229,25 @@ case $(file --mime-type --brief $timestamp_file) {
|
|||||||
_ output_data['chapters'].append(chapter)
|
_ output_data['chapters'].append(chapter)
|
||||||
}
|
}
|
||||||
;;
|
;;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strict_mode and has_error) { exit 1 }
|
# Exit if the script is set as strict and has erreneous input.
|
||||||
|
# If the user cares about the input, they have to set it to strict mode.
|
||||||
|
if (strict_mode and has_error) { exit 1 }
|
||||||
|
|
||||||
const title_slug = $(kebab-case $album)
|
# Set parts of the output data and prepare for the splitting process.
|
||||||
setvar output_data['directory'] = $(realpath --canonicalize-missing $title_slug)
|
const title_slug = $(kebab-case $album)
|
||||||
mkdir -p $title_slug
|
setvar output_data['directory'] = $(realpath --canonicalize-missing $title_slug)
|
||||||
|
mkdir -p $title_slug
|
||||||
|
|
||||||
# Rather than sequentially segmenting the audio, we'll extract the starting and ending timestamps of each segment then feed it to a job queue that can execute jobs in parallel.
|
# Rather than sequentially segmenting the audio, we'll extract the starting and ending timestamps of each segment then feed it to a job queue that can execute jobs in parallel.
|
||||||
# Take note we don't have the ending timestamp of each segment so we need a way to look back into items.
|
# Take note we don't have the ending timestamp of each segment so we need a way to look back into items.
|
||||||
const chapter_len = len(output_data['chapters'])
|
const chapter_len = len(output_data['chapters'])
|
||||||
var job_queue = %()
|
var job_queue = %()
|
||||||
|
|
||||||
for index in @(seq $[chapter_len]) {
|
# Iterate through the chapters and populate the job queue.
|
||||||
|
# We'll also fill up the rest of the chapter-related data into the output data.
|
||||||
|
for index in @(seq $[chapter_len]) {
|
||||||
var index = Int(index)
|
var index = Int(index)
|
||||||
var chapter = output_data['chapters'][index - 1]
|
var chapter = output_data['chapters'][index - 1]
|
||||||
var start = chapter['timestamp']
|
var start = chapter['timestamp']
|
||||||
@ -230,17 +255,24 @@ for index in @(seq $[chapter_len]) {
|
|||||||
var filename = $(printf "%.2d-%s.%s" $index $(kebab-case ${chapter['title']}) $EXTENSION)
|
var filename = $(printf "%.2d-%s.%s" $index $(kebab-case ${chapter['title']}) $EXTENSION)
|
||||||
setvar output_data['chapters'][index - 1]['file'] = filename
|
setvar output_data['chapters'][index - 1]['file'] = filename
|
||||||
|
|
||||||
# Check for incorrect timestamp order.
|
# Check for incorrect timestamp order and set the pipeline as erreneous if it is.
|
||||||
|
# We can't let the splitting process proceed since it will surely make problematic output.
|
||||||
if (start > end and end is not null) {
|
if (start > end and end is not null) {
|
||||||
warnf '%s (start) is ahead compared to %s (end)' $start $end
|
warnf '%s (start) is ahead compared to %s (end)' $start $end
|
||||||
setvar has_error = true
|
setvar has_error = true
|
||||||
}
|
}
|
||||||
|
|
||||||
append :job_queue ">&2 printf '[%d/%d] %s\\n' $[index] $[chapter_len] \"$[output_data['chapters'][index - 1]['title']]\"; ffmpeg -loglevel quiet -nostdin -i '${audio_file}' -ss ${start} $['-to ' + end if index != chapter_len else ''] ${title_slug}/${filename}"
|
append :job_queue ">&2 printf '[%d/%d] %s\\n' $[index] $[chapter_len] \"$[output_data['chapters'][index - 1]['title']]\"; ffmpeg -loglevel quiet -nostdin -i '${audio_file}' -ss ${start} $['-to ' + end if index != chapter_len else ''] ${title_slug}/${filename}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Exit the process if an error detected.
|
||||||
|
if (has_error) { exit 1 }
|
||||||
|
|
||||||
|
# Start the splitting process if the `--skip` is absent.
|
||||||
|
if (not skip) { parallel -- @job_queue }
|
||||||
|
|
||||||
|
# Print the output data as JSON if the `--json` flag is passed.
|
||||||
|
if (prints_json) { json write :output_data }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (has_error) { exit 1 }
|
main @ARGV
|
||||||
|
|
||||||
if (not skip) { parallel -- @job_queue }
|
|
||||||
if (prints_json) { json write :output_data }
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user