This is part of a series of posts.
- Normalizing FLAC files
- Normalizing FLAC files - with Ruby - Part I
- Normalizing FLAC files - with Ruby - Part II
- Normalizing FLAC files - with Ruby - Part III
Code relevant to this post is available at this repository.
I wrote about the script I use to normalize and reencode my FLAC files here.
I noted at the end that one of the issues with using bash scripts, or rather more specifically, with getopt, is that it has problems telling the difference between multiple arguments and arguments with spaces in them.
You can get around that using getopts, but then you give up --long-options.
If there’s one thing I try and avoid, it’s giving up options.
So, based on that, I concluded that the script is best rewritten in ruby or python. This weekend, I went ahead and did just that. The replacement ruby script is coming along pretty nicely. First, I wrote a class that will handle a single FLAC file. After checking that the file is real and valid, the script attempts to encode the file in-place. If this fails—typically due to the presence of an ID3v2 tag—the script proceeds to decode and re-encode the file. If it succeeded the first time, it just makes sure the replay gain value is correct and moves on. All this is handled by a single class.
I needed to test the handling of FLAC files with ID3 tags in them.
I know that kid3, my tag editor of choice, doesn’t show or add ID3 tags, so I’d need to find a file with one of those already present.
Since I’d fixed all the files in my library, I had to sift through my backups.
Sadly, my backups go only as far back as January 2014 and I’d fixed this issue in February 2013.
Damn!
I trawled the nets to see if I could find a FLAC file that had an intact ID3 tag.
After wasting far too much time doing this, I found out that simulating this using the id3tag utility is dead-easy.
$ id3tag --v2tag --artist=TEST foo.flac
That’s it.
foo.flac is now a FLAC file with an ID3v2 tag and flac will choke on it and trigger the re-encoding logic.
Here’s what this class looks like at the moment:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# Normalize a single FLAC file
class FlacFile
def initialize filePath
if File.file?( filePath )
if File.extname( filePath ) == ".flac"
@filePath = File.absolute_path( filePath )
@baseName = File.basename( @filePath )
puts @baseName
@dirName = File.dirname( @filePath )
@albumArt = File.file?( "#{@dirName}" + "/album.jpg" )
@validFile = true
%x( flac --silent --test "#{@filePath}" )
if $?.exitstatus != 0
@validFile = false
end
end
end
end
# Normalize FLAC files
def normalize
return if not @validFile
%x( flac --silent --force "#{@filePath}" )
if $?.exitstatus != 0
reencode
else
%x( metaflac --preserve-modtime --add-replay-gain "#{@filePath}" )
if $?.exitstatus != 0
puts "Replay gain error."
end
end
end
# Reencode FLAC files
def reencode
puts "Removing ID3 tags."
Dir.mktmpdir { |tmpDir|
FileUtils.cd( tmpDir ) do
FileUtils.cp( @filePath, "original.flac" )
# Decode file
# Export tags
%x( flac --silent --decode --output-name=original.wav original.flac )
%x( metaflac --export-tags-to=metadata.ini original.flac )
# Encode file
# Import tags
%x( flac --silent --force --output-name="#{@filePath}" original.wav )
%x( metaflac --import-tags-from=metadata.ini "#{@filePath}" )
end
# Embed album art, if found
if @albumArt
%x( metaflac --import-picture-from="#{@dirName}/album.jpg" "#{@filePath}" )
end
# Calculate replay gain
%x( metaflac --add-replay-gain "#{@filePath}" )
}
end
private :reencode
end
There are some nice things to note at this stage.
First, this script will check for valid FLAC files using the --test option of the flac utility.
Only if a file is valid will it try to normalize it.
Next, the blocks written with mktmpdir (requires tmpdir) and cd mean that at the end of the block, the temporary directory is cleaned up automatically and the current directory is also returned.
This, I find really convenient about ruby blocks.
Lastly, I’ve made the reencode function private to the class so it can’t be called from outside.
As a C++ person, having access modifiers within classes in ruby makes me feel right at home.
Looking forward to writing the directory/file processing bits next! The code is in the development branch of this repository alongside the original bash script.