feat(user/split_audiobook.sh):Split audiobooks by chapter
[BK-2020-03.git] / user / convert_file_to_flac.sh
1 #!/usr/bin/env bash
2 # Desc: Converts file readable by ffmpeg to FLAC audio file
3 # Usage: convert_file_to_flac.sh [path]
4 # Version: 0.0.3
5 # Ref/Attrib: [1] Convert audio file to FLAC with ffmpeg? https://superuser.com/a/802126
6 # [2] How to specify flac compression level when converting with avconv? https://askubuntu.com/questions/544651/
7 # [3] How can I extract audio from video with ffmpeg? https://stackoverflow.com/a/27413824
8 # Depends: ffmpeg version 4.4.2, ffprobe version 4.4.2, bash version 5.1.16
9
10 declare -Ag appRollCall # Associative array for storing app status
11 declare -Ag fileRollCall # Associative array for storing file status
12 declare -Ag dirRollCall # Associative array for storing dir status
13
14 yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
15 die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
16 try() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
17 checkapp() {
18 # Desc: If arg is a command, save result in assoc array 'appRollCall'
19 # Usage: checkapp arg1 arg2 arg3 ...
20 # Version: 0.1.1
21 # Input: global assoc. array 'appRollCall'
22 # Output: adds/updates key(value) to global assoc array 'appRollCall'
23 # Depends: bash 5.0.3
24 local returnState
25
26 #===Process Args===
27 for arg in "$@"; do
28 if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
29 appRollCall[$arg]="true";
30 if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
31 else
32 appRollCall[$arg]="false"; returnState="false";
33 fi;
34 done;
35
36 #===Determine function return code===
37 if [ "$returnState" = "true" ]; then
38 return 0;
39 else
40 return 1;
41 fi;
42 } # Check that app exists
43 checkfile() {
44 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
45 # Usage: checkfile arg1 arg2 arg3 ...
46 # Version: 0.1.1
47 # Input: global assoc. array 'fileRollCall'
48 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
49 # Output: returns 0 if app found, 1 otherwise
50 # Depends: bash 5.0.3
51 local returnState
52
53 #===Process Args===
54 for arg in "$@"; do
55 if [ -f "$arg" ]; then
56 fileRollCall["$arg"]="true";
57 if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
58 else
59 fileRollCall["$arg"]="false"; returnState="false";
60 fi;
61 done;
62
63 #===Determine function return code===
64 if [ "$returnState" = "true" ]; then
65 return 0;
66 else
67 return 1;
68 fi;
69 } # Check that file exists
70 checkdir() {
71 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
72 # Usage: checkdir arg1 arg2 arg3 ...
73 # Version 0.1.2
74 # Input: global assoc. array 'dirRollCall'
75 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
76 # Output: returns 0 if all args are dirs; 1 otherwise
77 # Depends: Bash 5.0.3
78 local returnState
79
80 #===Process Args===
81 for arg in "$@"; do
82 if [ -z "$arg" ]; then
83 dirRollCall["(Unspecified Dirname(s))"]="false"; returnState="false";
84 elif [ -d "$arg" ]; then
85 dirRollCall["$arg"]="true";
86 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
87 else
88 dirRollCall["$arg"]="false"; returnState="false";
89 fi
90 done
91
92 #===Determine function return code===
93 if [ "$returnState" = "true" ]; then
94 return 0;
95 else
96 return 1;
97 fi
98 } # Check that dir exists
99 displayMissing() {
100 # Desc: Displays missing apps, files, and dirs
101 # Usage: displayMissing
102 # Version 1.0.0
103 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
104 # Output: stderr: messages indicating missing apps, file, or dirs
105 # Output: returns exit code 0 if nothing missing; 1 otherwise
106 # Depends: bash 5, checkAppFileDir()
107 local missingApps value appMissing missingFiles fileMissing
108 local missingDirs dirMissing
109
110 #==BEGIN Display errors==
111 #===BEGIN Display Missing Apps===
112 missingApps="Missing apps :";
113 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
114 for key in "${!appRollCall[@]}"; do
115 value="${appRollCall[$key]}";
116 if [ "$value" = "false" ]; then
117 #echo "DEBUG:Missing apps: $key => $value";
118 missingApps="$missingApps""$key ";
119 appMissing="true";
120 fi;
121 done;
122 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
123 echo "$missingApps" 1>&2;
124 fi;
125 unset value;
126 #===END Display Missing Apps===
127
128 #===BEGIN Display Missing Files===
129 missingFiles="Missing files:";
130 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
131 for key in "${!fileRollCall[@]}"; do
132 value="${fileRollCall[$key]}";
133 if [ "$value" = "false" ]; then
134 #echo "DEBUG:Missing files: $key => $value";
135 missingFiles="$missingFiles""$key ";
136 fileMissing="true";
137 fi;
138 done;
139 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
140 echo "$missingFiles" 1>&2;
141 fi;
142 unset value;
143 #===END Display Missing Files===
144
145 #===BEGIN Display Missing Directories===
146 missingDirs="Missing dirs:";
147 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
148 for key in "${!dirRollCall[@]}"; do
149 value="${dirRollCall[$key]}";
150 if [ "$value" = "false" ]; then
151 #echo "DEBUG:Missing dirs: $key => $value";
152 missingDirs="$missingDirs""$key ";
153 dirMissing="true";
154 fi;
155 done;
156 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
157 echo "$missingDirs" 1>&2;
158 fi;
159 unset value;
160 #===END Display Missing Directories===
161
162 #==END Display errors==
163 #==BEGIN Determine function return code===
164 if [ "$appMissing" == "true" ] || [ "$fileMissing" == "true" ] || [ "$dirMissing" == "true" ]; then
165 return 1;
166 else
167 return 0;
168 fi
169 #==END Determine function return code===
170 } # Display missing apps, files, dirs
171 check_parsable_audio_ffprobe() {
172 # Desc: Checks if ffprobe returns valid audio codec name for file
173 # Usage: check_parsable_audio_ffprobe [path FILE]
174 # Version: 0.0.1
175 # Input: arg1: file path
176 # Output: exit code 0 if returns valid codec name; 1 otherwise
177 # Depends: ffprobe, die()
178 local file_in ffprobe_out
179
180 if [[ $# -ne 1 ]]; then die "ERROR:Invalid number of args:$#"; fi;
181
182 file_in="$1";
183
184 # Check if ffprobe detects an audio stream
185 if ffprobe -v error -select_streams a -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "$file_in" 1>/dev/null 2>&1; then
186 return_state="true";
187 else
188 return_state="false";
189 fi;
190
191 # Fail if ffprobe returns no result
192 ffprobe_out="$(ffprobe -v error -select_streams a -show_entries stream=codec_name -of default=nokey=1:noprint_wrappers=1 "$file_in")";
193 if [[ -z $ffprobe_out ]]; then
194 return_state="false";
195 fi;
196
197 # Report exit code
198 if [[ $return_state = "true" ]]; then
199 return 0;
200 else
201 return 1;
202 fi;
203 } # Checks if file has valid codec name using ffprobe
204 main() {
205 # Check inputs
206 if [[ ! -f $1 ]]; then die "FATAL:Not a file:$1"; fi;
207 if [[ $# -ne 1 ]]; then die "FATAL:Arguments not equal to 1:$#"; fi;
208
209 # Check depends
210 if ! checkapp ffmpeg ffprobe; then
211 displayMissing;
212 die "FATAL:Missing apps"; fi;
213
214
215 # Check file is parsable by ffprobe
216 if ! check_parsable_audio_ffprobe "$1"; then
217 die "FATAL:Not an audio file:$1"; fi;
218
219 # Convert file to FLAC. See [1], [2]
220 try ffmpeg -i "$1" -vn -c:a flac -compression_level 12 "$1".flac
221 }; # main program
222
223 main "$@";
224
225 # Author: Steven Baltakatei Sandoval
226 # License: GPLv3+