Thursday, April 23, 2009

Supply

Update: Added sorting and changed the order of assigning values to variables, so that manually set variables through command line options override those from the .supplyrc file. Oh, and also I added the verbose switch. (Apr 27, 11:44 PM)

Supposing you go on trains a lot, and you listen to audiobooks and/or podcasts when you do. I know of two problems that tend to arise then.
  • The trains are too damn loud and you can't actually hear your shows, even if you set your audio player on full blast and your hands over your ears.

  • You do this way too often and you get tired of shifting files by hand, especially if you've gotta do this file by file, since your audio player cares about the order in which files are loaded onto it, and your file system does not cooperate. Geek.
So it would be good if you could automate the process and apply some extra volume! And this is what this script does.

I've been putting it together for several weeks (I actually started last year and then got bored), scripting during lectures and so forth (when I should've really been paying attention to the complexities of MPEG-7) and came up with this wild bunch of code, all tied together.

It seems to work too!

It has several options:
  • Target is the device you want to put stuff on. In my case this can be /media/disk or /media/KINGSTON. I suggest fixing this up in the script itself (line 40) or, preferably, in the config file (more on that later).

  • Subdir(ectory) is a directory on the device that you want to put stuff in. I'd not set this up to fixed values anywhere, and would call it Books and Music. This would result in the files being placed into /media/disk/Books and /media/disk/Music, respectivelly.

  • Quiet will make the script not print out any info comments. It will still print out various warnings and assorted errors - if you want to desperately get rid of those redirect the error stream to /dev/null, like to ./supply 2> /dev/null

  • Gain probably the most important bit for us train-and-tram commuters. Sets a tag in ogg and mp3 files that tells the player to play louder. Setting gain to 5 will make it really loud... if you set it too loud it will start crackling like the bejesus...
Yeah, quite a few options, even if I say so myself...

But the purpose of this exercise to have it all simple! And that's where to config file comes in. You take the file called .supplyrc and put it in your home (~) directory (see line 41 in the source code). You put in the values you like into the config file, and you don't have to worry too much about configuration anymore! Isn't life grand?

So here's a simple example of use (supposing you're using my config file, as shown below):
./supply ~/Books/Nineteen\ Eighty-Four/ -s Books -g 2

This will put the audiobook into the directory /media/disk/Books/Nineteen\ Eighty-Four and increase the gain to 2, so it will be a bit louder.

Oh yeah! I almost forgot. You need to acquire vorbisgain and/or mp3gain to make oggs and/or mp3s louder. I usually install them via apt:
sudo apt-get install mp3gain vorbisgain

Gain modification will only work with oggs and mp3, and they have to have the appropriate extensions (because I have to tell them apart somehow) - ogg or oga, or mp3.

And another thing, this one's GPL v3 or more.

Here's the config file I made for myself and as a general template:
 
1 #!/bin/bash
2 # Filename: ~/.supplyrc
3 # Default settings for supply.sh
4
5 # Path to the device. Where to put the files by default.
6 # Needs to be a directory and end in a slash (/).
7 target=/media/disk/
8
9 # Where to put the files on the device pointed to by $target.
10 # Needs to be a directory and end in a slash (/).
11 subdir=Books/
12
13 # Gain setup. Leave commented out to have no gain by default.
14 # An arbitrary number.
15 # gain=0
16
17 # Quiet mode: print messages, or not.
18 # Allowed values: true, false.
19 # quiet=true


And here's the code:
 
1 #!/bin/bash
2
3 # Supply
4 #
5 # A script to supply your audio player with new audio files, with the
6 # extra ability to control gain in oggs and mp3s.
7 #
8 # Usage:
9 # t, target=<DIR> - Move media onto specified device
10 # d, subdirectory=<DIR> - Move media to a dir on the device (opt.)
11 # c, config=<FILE> - Apply specified configuration file (opt.)
12 # g, gain=<NUMBER - Apply specified gain value to all files (opt.)
13 # q, quiet - Do not print info messages (but still print warnings)
14 # v, verbose - Print info messages (opposite of quiet)
15 # <FILE LIST> - Act on these files and directories
16 #
17 # Requires:
18 # vorbisgain
19 # mp3gain
20 #
21 # Addendum regarding Ogg gain controls:
22 # vorbisgain input files must be Ogg Vorbis I files with 1 or 2
23 # channels and a sample rate of 48 kHz, 44.1 kHz, 32 kHz, 24
24 # kHz, 22050 Hz, 16 kHz, 12 kHz, 11025 Hz or 8 kHz. If an input
25 # file contains multiple streams (i.e., it is chained), the
26 # streams must all have the same format, in terms of sampling
27 # frequency and number of channels. All streams in a chained file
28 # are processed, but the ReplayGain tags are only written to (or
29 # removed from) the first stream.
30 # -- vorbisgain(1)
31 # License:
32 # Copyright 2009 Konrad Siek <konrad.siek@gmail.com>
33 # This program is free software: you can redistribute it and/or
34 # modify it under the terms of the GNU General Public License as
35 # published by the Free Software Foundation, either version 3 of
36 # the License, or (at your option) any later version. See
37 # <http://www.gnu.org/licenses/> for details.
38
39 # Default settings
40 target=/media/disk
41 config=~/.supplyrc
42 source=.
43 quiet='false'
44 #subdir=Books
45
46 # Echo function with possible silencing
47 function sysout {
48 if [ "$quiet" != 'true' ]
49 then
50 echo "$0: $1"
51 fi
52 }
53
54 # Echo function redirecting to standard error
55 function syserr {
56 echo "$0: $1" >& 2
57 }
58
59 # Parse options
60 options=$(\
61 getopt \
62 -o c:t:g:d:q:v \
63 --long gain:,target:,config:,subdirectory:,quiet,verbose \
64 -n $0 -- "$@" \
65 )
66
67 # Stop if there's some sort of problem
68 if [ $? != 0 ]
69 then
70 syserr "Argh! Parsing went pear-shaped!"
71 exit 1
72 fi
73
74 # Set the parsed command options
75 eval set -- "$options"
76
77 # Setup selected options
78 while [ 1 ]
79 do
80 case "$1" in
81 -c|--config)
82 config=$2
83 shift 2
84 ;;
85 -g|--gain)
86 custom_gain=$2
87 shift 2
88 ;;
89 -t|--target)
90 custom_target=$2
91 shift 2
92 ;;
93 -d|--subdirectory)
94 custom_subdir=$2
95 shift 2
96 ;;
97 -q|--quiet)
98 custom_quiet='true'
99 shift
100 ;;
101 -v|--verbose)
102 custom_quiet='false'
103 shift
104 ;;
105 --)
106 # Stop parsing options
107 shift
108 break
109 ;;
110 *)
111 # Weird error
112 syserr "Hide! It's gonna blow!"
113 exit 2
114 ;;
115 esac
116 done
117
118 # Source config file if one exists
119 if [ -f $config ]
120 then
121 sysout "Sourcing configuration file: $config"
122 . "$config"
123 else
124 sysout "No configuration file present."
125 fi
126
127 # Apply the so-called custom settings
128 if [ $custom_gain ]; then gain=$custom_gain; fi
129 if [ $custom_target ]; then target=$custom_target; fi
130 if [ $custom_subdir ]; then subdir=$custom_subdir; fi
131 if [ $custom_quiet ]; then quiet=$custom_quiet; fi
132
133 # Create full path
134 fullpath="$target/$subdir"
135
136 # If destination doesn't exist...
137 if [ -e $fullpath ]
138 then
139 # Ok, so there is something there...
140 if [ ! \( -d $fullpath \) ]
141 then
142 # ...but that something is not a directory!
143 syserr "$fullpath is not a directory"
144 exit 4
145 fi
146 if [ ! \( -w $fullpath \) ]
147 then
148 # ...but we can't write there!
149 syserr "No write permission for $fullpath"
150 exit 8
151 fi
152 else
153 # Create destination
154 mkdir -p $fullpath 2> /dev/null
155 if [ "$?" != 0 ]
156 then
157 syserr "Can't create destination $fullpath"
158 exit 16
159 fi
160 fi
161
162 # Apply gain, you daft bugger.
163 # Seriously, do I have to spell it out for you?
164 function gain {
165 # Resolve extension
166 ext=$(echo "$1" | awk -F "." '{print $NF}')
167
168 # Use the correct damn gain modification tool
169 if [ \( "$ext" == "ogg" \) -o \( "$ext" == "oga" \) ]
170 then
171 # Print short info
172 sysout "Setting gain to $gain for $(basename "$1") using vorbisgain"
173
174 # Run gain update for oggs
175 vorbisgain -q -g $gain "$1" 2> /dev/null
176 elif [ "$ext" == "mp3" ]
177 then
178 # Print short info
179 sysout "Setting gain to $gain for $(basename "$1") using mp3gain"
180
181 # Run gain update for mp3s
182 mp3gain -k -q -g $gain "$1" 2> /dev/null
183 else
184 syserr "Skiping gain modification for $(basename "$1")"
185 fi
186 }
187
188 # Process an unknown file type
189 # (Direcotries are also a kind of file...)
190 function resolve {
191 relative=$topdir${1:$cutoff}
192 #echo $cutoff $relative
193
194 if [ ! -d "$1" ]
195 then
196 sysout "Copying $(basename "$1") to $(dirname "$fullpath/$relative")"
197
198 # Copy a file to destination
199 cp "$1" "$fullpath/$relative"
200
201 # Apply gain modification
202 if [ "$gain" != "" ]
203 then
204 gain "$fullpath/$relative"
205 fi
206 else
207 sysout "Creating directory $fullpath/$relative"
208
209 # Create a directory at destination
210 mkdir -p "$fullpath/$relative"
211
212 # Copy stuff
213 cp_dir "$1"
214
215 # TODO This could be optional...
216 fi
217 }
218
219 # Process a directory
220 function cp_dir {
221 find "$1" | sort | while read f
222 do
223 # Do NOT work on your own self, dumbass!
224 if [ "$f" == "$1" ]
225 then
226 continue
227 fi
228
229 # But work on everything else, accordingly
230 resolve "$f"
231 done
232 }
233
234 # Copy files from the sources to the destination directory
235 if [ "$#" == 0 ]
236 then
237 # If no paths were given, use the current directory
238 cp_dir "."
239 else
240 for arg
241 do
242 cutoff=${#arg}
243 topdir=$(basename "$arg")/
244
245 resolve "$arg"
246 done
247 fi


The code is also available at GitHub as bash/.supplyrc and bash/supply.

Monday, April 13, 2009

Convert file encodings

Here's to a new bit of code!

I wrote this program once, called Tester, which my dad is using in his day-to-day language teaching.

I wrote the thing quite a long time ago, so it's a bit buggy here and there, and one of the problems is that it doesn't handle encodings well. Since dad is thinking about using Ubuntu, the mishandled encodings are a problem: he used to have them encoded as WINDOWS-1250, but the current settings allow him to use only UTF-8 on Ubuntu.

Hence, this program, to convert between the two. Since the files come in bulk, I figured doing entire directories at once is a good idea.

Also, I wrote it in about an hour and a bit, so it might be a bit buggy here and there...

Here's the code:
 
1 #!/bin/bash
2
3 # Convert directory
4 #
5 # Convert text files in the directory from one encoding to another,
6 # all in a simple GUI. Converts between UTF-8 and WINDOWS-1250.
7 #
8 # Requires:
9 # zenity
10 # iconv
11 # Author:
12 # Konrad Siek
13
14
15 # Get directory to convert files in
16 directory=$(\
17 zenity \
18 --file-selection \
19 --directory \
20 --title="Select a directory to convert" \
21 )
22
23 # If none selected, quit.
24 if [ $? != 0 ]
25 then
26 exit 1
27 fi
28
29 # Select source encoding from a list.
30 source_encoding=$(\
31 zenity --list \
32 --column="Encoding" \
33 --title="Source encoding" \
34 --text="The files are currently encoded as... " \
35 WINDOWS-1250 UTF-8 \
36 )
37
38 # If none selected, quit.
39 if [ $? != 0 ]
40 then
41 exit 1
42 fi
43
44 # Select destination encoding from a list.
45 destination_encoding=$(\
46 zenity --list \
47 --column="Encoding" \
48 --title="Destination encoding" \
49 --text="And you want these files encoded as... " \
50 UTF-8 WINDOWS-1250 \
51 )
52
53 # If none selected, quit.
54 if [ $? != 0 ]
55 then
56 exit 1
57 fi
58
59 # For all files in the selected directory...
60 find "$directory" -type f | while read f
61 do
62 # Get information about the file.
63 extension=${f#*.}
64 basename=$(basename "$f" ".$extension")
65 addition=$(echo "$destination_encoding" | tr -d - | tr [A-Z] [a-z])
66 output="$directory/$basename.$addition.$extension"
67
68 # Convert encoding.
69 iconv \
70 --from-code="$source_encoding" \
71 --to-code="$destination_encoding" \
72 --output="$output" \
73 "$f"
74
75 echo "Created $directory/$basename.$addition.$extension"
76 done
77
78 # Notify on finish
79 zenity --info --text="Operation complete." --title="Complete"
80


The code is also available at GitHub as bash/convert_directory.