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.