Kjetil's Information Center: A Blog About My Projects

FLAC to MP3 Conversion

Here is my custom Python script to convert FLAC files to MP3 files.
In addition to the obligatory audio conversion, it also handles tag conversion. Because of this, the external eyeD3 module is required.

Take a look:

#!/usr/bin/python

import subprocess
import eyed3
import os
import os.path
import re
import struct
import getopt

temp_tags = r"/tmp/___TAGS___.utf8"
temp_cover = r"/tmp/___COVER___.jpg"

def apic_from_unicode_to_latin1(filename):
    data = open(filename, "rb").read()

    match = re.search('APIC(......)\x01image/jpeg\x00\x03\xff\xfe\x00\x00', data)
    if match:
        header = match.group(1)

        # Just inject FIX as picture description to keep same frame size!
        old = 'APIC' + header + '\x01image/jpeg\x00\x03\xff\xfe\x00\x00'
        new = 'APIC' + header + '\x00image/jpeg\x00\x03FIX\x00'
        data = data.replace(old, new)

        fh = open(filename, "wb")
        fh.write(data)
        fh.close()

class TagData(object):
    def __init__(self):
        self.title = None
        self.album = None
        self.track_num = None
        self.artist = None
        self.genre = None
        self.date = None
        self.album_artist = None

    def read_flac_tags(self, filename):
        subprocess.call(["metaflac", "--no-utf8-convert", "--export-tags-to=" + temp_tags, filename])
        fh = open(temp_tags, "rb")
        for line in fh:
            match = re.match(r'^TITLE=(.*)', line.decode('UTF-8'), re.IGNORECASE)
            if match:
                self.title = match.group(1)
            match = re.match(r'^ALBUM=(.*)', line.decode('UTF-8'), re.IGNORECASE)
            if match:
                self.album = match.group(1)
            match = re.match(r'^TRACKNUMBER=(\d*)', line.decode('UTF-8'), re.IGNORECASE)
            if match:
                self.track_num = int(match.group(1))
            match = re.match(r'^ARTIST=(.*)', line.decode('UTF-8'), re.IGNORECASE)
            if match:
                self.artist = match.group(1)
            match = re.match(r'^GENRE=(.*)', line.decode('UTF-8'), re.IGNORECASE)
            if match:
                self.genre = match.group(1)
            match = re.match(r'^DATE=(\d*)', line.decode('UTF-8'), re.IGNORECASE)
            if match:
                self.date = int(match.group(1))

        fh.close()

    def write_mp3_tags(self, filename):
        audiofile = eyed3.load(filename)
        audiofile.tag = eyed3.id3.Tag() # Create NEW!
        if self.title:
            audiofile.tag.title = self.title
        if self.album:
            audiofile.tag.album = self.album
        if self.track_num:
            audiofile.tag.track_num = self.track_num
        if self.artist:
            audiofile.tag.artist = self.artist
        if self.genre:
            audiofile.tag.genre = self.genre
        if self.date:
            audiofile.tag.recording_date = self.date # Works if version 2.3
        if self.album_artist:
            audiofile.tag.album_artist = self.album_artist
        audiofile.tag.save(filename, eyed3.id3.ID3_V2_3)

def print_usage(progname):
    print "Usage: %s " % (progname)
    print "-f <FLAC File>"
    print "-a [AlbumArtist]"
    print "-c [Cover Image Path]"
    print "-n [Album Name Override]"

if __name__ == "__main__":
    import sys

    tagdata = TagData()

    # NOTE: Need to filter out empty strings from sys.argv.
    # If not, the parser thinks parsing finishes early.
    try:
        opts, args = getopt.getopt(filter(None, sys.argv[1:]), "hf:a:c:n:", ["help", "flacfile=", "albumartist=", "cover=", "albumname="])
    except getopt.GetoptError as err:
        print str(err)
        print_usage(sys.argv[0])
        sys.exit(1)

    print "Opts:", opts
    print "Args:", args

    filename = None
    album_artist = None
    cover_override = None
    album_name_override = None

    for o, a in opts:
        if o in ("-h", "--help"):
            print_usage(sys.argv[0])
            sys.exit(1)
        elif o in ("-f", "--flacfile"):
            filename = a
            print "FLAC File:", filename
        elif o in ("-a", "--albumartist"):
            album_artist = a
            print "AlbumArtist:", album_artist
        elif o in ("-c", "--cover"):
            cover_override = a
            print "Cover Image Path:", cover_override
        elif o in ("-n", "--albumname"):
            album_name_override = a
            print "Album Name Override:", album_name_override

    if filename == None:
        print_usage(sys.argv[0])
        sys.exit(1)

    # Set optional AlbumArtist.
    if album_artist:
        tagdata.album_artist = unicode(album_artist)

    # Read old tags from FLAC.
    tagdata.read_flac_tags(filename)

    # Set optional album name override, AFTER reading default tags.
    if album_name_override:
        tagdata.album = unicode(album_name_override)

    # Decode FLAC, encode MP3.
    subprocess.call(["flac", "-d", filename, "-o", filename + ".wav"])
    subprocess.call(["lame", "-b", "320", "-h", filename + ".wav", filename + ".mp3"])

    # Write new tags to MP3.
    tagdata.write_mp3_tags(filename + ".mp3")

    # Get cover image from argument, or...
    if cover_override:
        cover_file = cover_override
    else:
        # ...attempt to extract cover image from FLAC file...
        subprocess.call(["metaflac", "--export-picture-to=" + temp_cover, filename])
        cover_file = temp_cover

    # ...then apply it.
    if os.path.isfile(cover_file):
        imagedata = open(cover_file, "rb").read()
        if imagedata.startswith("\xff\xd8\xff"):
            audiofile = eyed3.load(filename + ".mp3")
            audiofile.tag.images.set(3, imagedata, "image/jpeg")
            audiofile.tag.save()
        else:
            print "Warning: Image data is not JPEG."

    # iTunes APIC fix, encoding must be changed to latin1.
    apic_from_unicode_to_latin1(filename + ".mp3")

    # Remove ".flac" part of final MP3 file.
    if os.path.isfile(filename + ".mp3"):
        os.rename(filename + ".mp3", filename.replace(".flac", "") + ".mp3")

    # Remove temporary files.
    if os.path.isfile(filename + ".wav"):
        os.remove(filename + ".wav")
    if os.path.isfile(temp_tags):
        os.remove(temp_tags)
    if os.path.isfile(temp_cover):
        os.remove(temp_cover)
          


Topic: Scripts and Code, by Kjetil @ 03/12-2016, Article Link