90

I've got a full album flac and a cue file for it. How can I split this into a flac per track?

I'm a KDE user, so I would prefer a KDE/Qt way. I would like to see command line and other GUI answers as well, but they are not my preferred method.

1

10 Answers 10

111

Shnsplit can read a cue file directly, which also means it can access the other data from the cue file (not just the breakpoints) and generate nicer filenames than split-*.flac:

shnsplit -f file.cue -t %n-%t -o flac file.flac

Granted, this makes it more difficult to use cuetag.sh if the original flac file is in the same directory.

6
  • 29
    Fellow debian users: sudo apt-get install cuetools shntool
    – jwbensley
    May 3, 2014 at 12:47
  • 24
    You probably also want to tag the files with cuetag file.cue [0-9]*.flac Aug 28, 2014 at 14:41
  • 13
    I also needed: sudo apt-get install flac
    – becko
    Mar 28, 2016 at 10:13
  • 2
    cuetools already depends on flac, so … Jan 5, 2020 at 16:30
  • 1
    On Debian 10 there is a bug with 24 bits flacs. It won't split. It works fine on 16 bits files though. Dec 14, 2020 at 17:05
25

Flacon is an intuitive open-source GUI that does exactly that: split a FLAC with a CUE.

Flacon extracts individual tracks from one big audio file containing the entire album of music and saves them as separate audio files. To do this, it uses information from the appropriate CUE file.

It supports among other things:

Supported input formats: WAV, FLAC, APE, WavPack, True Audio (TTA).

Supported out formats: FLAC, WAV, WavPack, AAC, OGG or MP3.

Automatic character set detection for CUE files.

To use it you only need to open the *.cue file with Flacon. It should then automatically detect the big *.flac file (if not, you can specify this manually), and then you should select Flac output format (and optionally configure the encoder), and start the conversion process.

Flacon v5.4.0

5
  • 1
    Welcome to Unix & Linux Stack Exchange! Whilst this may theoretically answer the question, it would be preferable to include the essential parts of the answer here, and provide the link for reference.
    – slm
    Nov 3, 2013 at 15:55
  • still updated in 2017
    – user32012
    Sep 28, 2017 at 16:45
  • 2
    I don't like to use UI for things I can do on the terminal, but I was struggling with an album, and this was the only way of splitting the files. You deserve a beer now man
    – coffekid
    Apr 25, 2020 at 3:33
  • Unfortunately, Flacon doesn't support 32/192 format. I have a Vinyl Rip which came as a wavepack in 32/192 format and Flacon throws an error that the resolution is too high. Aug 31, 2021 at 1:01
  • Thanks for this answer. I've been looking for a tool exactly like this for a long time now. Excellent recommendation. Sep 13, 2021 at 5:59
24

I only know a CLI way. You will need cuetools and shntool.

cuebreakpoints file.cue | shnsplit -o flac file.flac
cuetag.sh file.cue "split-*".flac
2
  • 2
    Thanks for this Kambus. I've been using cuebreakpoints file.cue | shnsplit -o flac file.flac for a long time. The 2nd bit is going to help a lot!
    – boehj
    Apr 12, 2011 at 6:44
  • 1
    cuetag seems to break on filenames containing spaces but after removing them it worked.
    – scai
    Nov 20, 2013 at 17:40
9

if high-quality files are being used, shnsplit is happily erroring out with

shnsplit: error: m:ss.ff format can only be used with CD-quality files

fortunately the flac binary supports --skip=mm:ss.ss and --until=mm:ss.ss so a script can use cuebreakpoints like this:

[..]
time[0]="00:00.00"
c=1
for ts in $(cuebreakpoints "${cue_file}"); do
    time[${c}]=${ts}
    c=$((c+1))
done
time[${c}]='-0'
for ((i=0;i<$((${#time[@]}-1));i++)); do
    trackno=$(($i+1))
    TRACKNUMBER="$(printf %02d ${trackno})"
    title="$(cueprint --track-number ${trackno} -t '%t' "${cue_file}")"
    flac --silent --exhaustive-model-search --skip=${time[$i]} --until=${time[$(($i+1))]} --tag=ARTIST="${ARTIST}" --tag=ALBUM="${ALBUM}" --tag=DATE="${DATE}" --tag=TITLE="${title}" --tag=TRACKNUMBER="${TRACKNUMBER}" "${aud_file}" --output-name="${TRACKNUMBER}-${title}.flac"
done
1
  • This is the only method that worked for me.
    – coffekid
    Feb 26, 2018 at 2:57
3

If you have cue set to use k3b in in the filetype settings, k3b will automatically split the file if you open the cue file, and allow you to re-rip.

2

There is a project that works for several input files: split2flac

From the project description:

split2flac splits one big APE/FLAC/TTA/WV/WAV audio image (or a collection of such files, recursively) with CUE sheet into FLAC/M4A/MP3/OGG_VORBIS/WAV tracks with tagging, renaming, charset conversion of cue sheet, album cover images. It also uses configuration file, so no need to pass a lot of arguments every time, only an input file. Should work in any POSIX-compliant shell.

2

Here is a PHP script:

<?php
$s_cue = $argv[1];
$a_cue = file($s_cue);
$n_row = -1;
foreach ($a_cue as $s_row) {
   $s_trim = trim($s_row);
   $a_row = str_getcsv($s_trim, ' ');
   if (preg_match('/^FILE\s/', $s_row) == 1) {
      $s_file = $a_row[1];
   }
   if (preg_match('/^\s+TRACK\s/', $s_row) == 1) {
      $n_row++;
      $a_table[$n_row]['track'] = $a_row[1];
   }
   if (preg_match('/^\s+TITLE\s/', $s_row) == 1) {
      $a_table[$n_row]['title'] = $a_row[1];
   }
   if (preg_match('/^\s+PERFORMER\s/', $s_row) == 1) {
      $a_table[$n_row]['artist'] = $a_row[1];
   }
   if (preg_match('/^\s+INDEX\s/', $s_row) == 1) {
      $s_dur = $a_row[2];
      $a_frame = sscanf($s_dur, '%d:%d:%d', $n_min, $n_sec, $n_fra);
      $n_index = $n_min * 60 + $n_sec + $n_fra / 75;
      $a_table[$n_row]['ss'] = $n_index;
      if ($n_row > 0) {
         $a_table[$n_row - 1]['to'] = $n_index;
      }
   }
}
$a_table[$n_row]['to'] = 10 * 60 * 60;
foreach ($a_table as $m_row) {
   $a_cmd = [
      'ffmpeg',
      '-i', $s_file,
      '-ss', $m_row['ss'],
      '-to', $m_row['to'],
      '-metadata', 'artist=' . $m_row['artist'],
      '-metadata', 'title=' . $m_row['title'],
      '-metadata', 'track=' . $m_row['track'],
      $m_row['track'] . ' ' . $m_row['title'] . '.m4a'
   ];
   $a_esc = array_map('escapeshellarg', $a_cmd);
   $s_esc = implode(' ', $a_esc);
   system($s_esc);
}
1

I found mac (which is the command that shntool used for decoding APE files) is way less tolerant than ffmpeg if the source file contains minor errors.

Normally ffmpeg would still convert the file completely while mac very likely throws an error during the processing.

So I ended up writing a script for spliting APE file by parsing the CUE file and converting the APE file to FLAC files separated by titles using ffmpeg:

#!/usr/bin/env python2.7

import subprocess as subp
import sys
import os
from os.path import splitext, basename
import random
import glob

records = []
filename = ""
album=''
alb_artist=''
codec = 'flac'
ffmpeg_exec = 'ffmpeg'
encodingList = ('utf-8','euc-kr', 'shift-jis', 'cp936', 'big5')

filecontent = open(sys.argv[1]).read()
for enc in encodingList:
    try:
        lines = filecontent.decode(enc).split('\n')
        encoding = enc
        break
    except UnicodeDecodeError as e:
        if enc == encodingList[-1]:
            raise e
        else:
            pass

for l in lines:
    a = l.split()
    if not a:
        continue
    if a[0] == "FILE":
        filename = ' '.join(a[1:-1]).strip('\'"')
    elif a[0]=='TRACK':
        records.append({})
        records[-1]['index'] = a[1]
    elif a[0]=='TITLE':
        if len(records)>0:
            records[-1]['title'] = ' '.join(a[1:]).strip('\'"')
        else:
            album =  ' '.join(a[1:]).strip('\'"')
    elif a[0]=='INDEX' and a[1]=='01':
        timea = a[2].split(':')
        if len(timea) == 3 and int(timea[0]) >= 60:
            timea.insert(0, str(int(timea[0])/60))
            timea[1] = str(int(timea[1])%60)
        times = '{0}.{1}'.format(':'.join(timea[:-1]), timea[-1])
        records[-1]['start'] = times
    elif a[0]=='PERFORMER':
        if len(records)>1:
            records[-1]['artist'] = ' '.join(a[1:]).strip('\'"')
        else:
            alb_artist = ' '.join(a[1:]).strip('\'"')

for i, j in enumerate(records):
    try:
        j['stop'] = records[i+1]['start']
    except IndexError:
        pass

if not os.path.isfile(filename):
    tmpname = splitext(basename(sys.argv[1]))[0]+splitext(filename)[1]
    if os.path.exists(tmpname):
        filename = tmpname
        del tmpname
    else:
        for ext in ('.ape', '.flac', '.wav', '.mp3'):
            tmpname = splitext(filename)[0] + ext
            if os.path.exists(tmpname):
                filename = tmpname
                break

if not os.path.isfile(filename):
    raise IOError("Can't not find file: {0}".format(filename))

fstat = os.stat(filename)
atime = fstat.st_atime
mtime = fstat.st_mtime

records[-1]['stop'] = '99:59:59'

if filename.lower().endswith('.flac'):
    tmpfile = filename
else:
    tmpfile = splitext(filename)[0] + str(random.randint(10000,90000)) + '.flac'

try:
    if filename != tmpfile:
        ret = subp.call([ffmpeg_exec, '-hide_banner', '-y', '-i', filename, 
            '-c:a', codec,'-compression_level','12','-f','flac',tmpfile])

        if ret != 0:
            raise SystemExit('Converting failed.')

    for i in records:
        output = i['index'] +' - '+ i['title']+'.flac'
        commandline = [ffmpeg_exec, '-hide_banner', 
        '-y', '-i', tmpfile,
        '-c', 'copy', 
        '-ss', i['start'], '-to', i['stop'],
        '-metadata', u'title={0}'.format(i['title']), 
        '-metadata', u'artist={0}'.format(i.get('artist', '')),
        '-metadata', u'performer={0}'.format(i.get('artist', '')),
        '-metadata', u'album={0}'.format(album), 
        '-metadata', 'track={0}/{1}'.format(i['index'], len(records)), 
        '-metadata', u'album_artist={0}'.format(alb_artist), 
        '-metadata', u'composer={0}'.format(alb_artist), 
        '-metadata', 'encoder=Meow', 
        '-write_id3v1', '1', 
        output]
        ret = subp.call(commandline)
        if ret == 0:
            os.utime(output, (atime, mtime))
finally:
    if os.path.isfile(tmpfile):
        os.remove(tmpfile)
2
  • You might want to change if os.path.isfile(tmpfile) to if tmpfile != filename and os.path.isfile(tmpfile) to avoid deleting the original file on error.
    – wolfmanx
    Jan 19, 2018 at 6:26
  • The condition for setting the track artist must be if len(records)>0.
    – wolfmanx
    Jan 19, 2018 at 6:41
1

shntool on Ubuntu 14.04

snhtool is missing the mac (Monkey's Audio Console) executable dependency, and the only package I could find it was in the flacon PPA:

sudo add-apt-repository -y ppa:flacon
sudo apt-get update
sudo apt-get install -y flacon shntool
shntool split -f *.cue -o flac -t '%n - %p - %t' *.ape

flacon is a GUI for shntool, but it comes with all the codecs it needs... otherwise I got the error:

shnsplit: warning: failed to read data from input file using format: [ape]
shnsplit:          + you may not have permission to read file: [example.ape]
shnsplit:          + arguments may be incorrect for decoder: [mac]
shnsplit:          + verify that the decoder is installed and in your PATH
shnsplit:          + this file may be unsupported, truncated or corrupt
shnsplit: error: cannot continue due to error(s) shown above
1
  • On Debian Buster, adding debian-multimedia.org repository and installing monkeys-audio package did the trick. Jul 12, 2021 at 22:07
0

unflac automatically splits into flac and also does the tagging right!

You need go installed and then run it by giving the cue as input: ~/go/bin/unflac filename.cue.

I found out about it after files I split with other tools where not playable.

Not the answer you're looking for? Browse other questions tagged or ask your own question.