#!/usr/bin/perl

use warnings;
use strict;
use utf8;
use Encode;
use open ":locale";

sub usage{
    print << "EOT"
usage: $0 CUEFILE.cue [:ENCODING] [WAV-OVERRIDE] [+FORMAT] [- FFMPEG-OPTIONS...]

Encoding:
    This is PerlIO layer selector.
    You can use :encoding(CHARCODE) to set cue file charcode.

Wav override:
    Normally, this program reads audio from file which specified by cue.
    You can override this filename.

Format:
    Output filename format.
    If not spicified, use "+%n-%02t.ogg"
    %[0-9]*n: Track number
    %t      : Track name
    %p      : Track performer
    %T      : Album name
    %P      : Album performer

FFmpeg options:
    Pass these options to FFmpeg as output file options.
    If not specified, use "-c:a libopus -b:a 64k -vn -sn"
EOT
;
    exit(shift|0);
}

my $cuename;
my $cueenc = "";
my $wavor;
my $fnpat;
my @encops;
for(@ARGV){
    if(/^(?:-h|--help|-help|-\?)$/){
        usage(0);
    }elsif(/^-/ .. 0){
        push(@encops, $_);
    }elsif(!defined($fnpat) && /^\+/){
        $fnpat = substr($_, 1);
    }elsif(!length($cueenc) && /^:/){
        $cueenc = $_;
    }elsif(!defined($cuename)){
        $cuename = $_;
    }elsif(!defined($wavor)){
        $wavor = $_;
    }else{
        usage(1);
    }
}
if(!defined($fnpat)){
    $fnpat = "%n-%02t.ogg";
}
if(!@encops){
    @encops = qw(-c:a libopus -b:a 64k -vn -sn);
}
shift(@encops) if(@encops && $encops[0] eq "-");

if(!defined($cuename)){
    usage(1);
}

my @tracks;
my $disc = {};
my $pt = $disc;
my $cue;
open($cue, "<$cueenc", $cuename) || die "cue open error";
while(<$cue>){
    my @lt;
    while(/(?|([^\s"]+)|"((?:[^"]|(?<=(?<!\\)\\)")*)")/g){
        push(@lt, $1);
        $lt[$#lt] =~ s/\\([\\"])/$1/g;
    }
    if($lt[0] eq "PERFORMER" || $lt[0] eq "TITLE"){
        $pt->{lc($lt[0])} = $lt[1];
    }elsif($lt[0] eq "FILE"){
        $wavor = $lt[1] if(!defined($wavor));
    }elsif($lt[0] eq "INDEX"){
        if($lt[2] !~ /^([0-9]{2}):([0-9]{2}):([0-9]{2})$/){
            die "index error";
        }
        my $s = $1 * 60 + $2 + $3 / 75;
        if($lt[1] == 0){
            $pt->{prgap} = $s;
        }elsif($lt[1] == 1){
            $pt->{start} = $s;
            $pt->{prgap} = $s if(!defined($pt->{prgap}));
        }
    }elsif($lt[0] eq "TRACK"){
        if($tracks[$lt[1]]){
            die "track has same number";
        }
        $pt = {num => +$lt[1]};
        $tracks[$lt[1] - 1] = $pt;
    }
}

if(!defined($wavor)){
    die "give audio file";
}

for(0 .. $#tracks){
    my $track = $tracks[$_];
    if(!$track){
        next;
    }
    my @lo;
    @lo = ("-t", $tracks[$_+1]{prgap} - $track->{start}) if($tracks[$_+1]);
    my %ma = (
        track => $track->{num},
        title => $track->{title},
        artist => $track->{performer},
        album => $disc->{title},
        album_artist => $disc->{performer}
    );
    my @metaops;
    while(my ($k, $v) = each(%ma)){
        push(@metaops, "-metadata", "$k=$v") if(defined($v));
    }
    my %fa = (
        n => $track->{num},
        t => $track->{title},
        p => $track->{performer},
        T => $disc->{title},
        P => $disc->{performer}
    );
    my $fh = sub{
        my $l = shift || 0;
        my $n = shift;
        my $p = ($l =~ /^0/) ? "0" : " ";
        my $r = defined($fa{$n}) ? $fa{$n} : "";
        my $a = $l - length($r);
        $a = 0 if($a < 0);
        return ($p x $a) . $r;
    };
    my $fname = $fnpat;
    $fname =~ s/%([0-9]*)([ntpTP])/$fh->($1, $2)/ge;
    system("ffmpeg", "-ss", $track->{start},
                     "-i", $wavor, @metaops, @encops, @lo, $fname);
}