Commit | Line | Data |
---|---|---|
c13b0ce8 | 1 | #!/usr/bin/env bash |
6731e5ce | 2 | # Desc: Converts file readable by ffmpeg to FLAC audio file |
54469ce4 | 3 | # Usage: convert_file_to_flac.sh [path] |
83eab65c | 4 | # Version: 0.0.4 |
54469ce4 | 5 | # Ref/Attrib: [1] Convert audio file to FLAC with ffmpeg? https://superuser.com/a/802126 |
6731e5ce SBS |
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 | |
c13b0ce8 | 8 | # Depends: ffmpeg version 4.4.2, ffprobe version 4.4.2, bash version 5.1.16 |
54469ce4 SBS |
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 | ||
6731e5ce | 219 | # Convert file to FLAC. See [1], [2] |
83eab65c | 220 | try ffmpeg -i "$1" -vn -c:a flac -map_metadata 0 -compression_level 12 "$1".flac |
54469ce4 SBS |
221 | }; # main program |
222 | ||
223 | main "$@"; | |
224 | ||
225 | # Author: Steven Baltakatei Sandoval | |
226 | # License: GPLv3+ |