]> diplodocus.org Git - flac-archive/commitdiff
Mar 18 2022
authorEric Gillespie <epg@timescale.com>
Tue, 11 Oct 2022 00:09:46 +0000 (19:09 -0500)
committerEric Gillespie <epg@timescale.com>
Tue, 11 Oct 2022 00:09:46 +0000 (19:09 -0500)
fa-mp3cd.tested [new file with mode: 0755]

diff --git a/fa-mp3cd.tested b/fa-mp3cd.tested
new file mode 100755 (executable)
index 0000000..81568f9
--- /dev/null
@@ -0,0 +1,311 @@
+#!/usr/local/bin/perl
+
+# TODO This is the one I've actually used.  The other I hacked up and maybe improved?
+#  but haven't tested
+
+=head1 NAME
+
+B<fa-mp3cd> - Make Universal Disk Format image of MP3 files from FLAC files
+
+=head1 SYNOPSIS
+
+B<fa-mp3cd> [B<-d> I<work-directory>] [B<-o> I<image-file>] [B<-V> I<VOLI>] I<file> [...]
+
+=head1 DESCRIPTION
+
+Each I<file> argument is a single-track[1] FLAC file to be transcoded to MP3
+and stored in the UDF image.  By default, place MP3 files in directories named
+after the album title.  Change with B<-dirname> options tracks after the
+B<-dirname> go into that dirname.  Restore default with B<-diralbum>.[2]
+
+B<fa-mp3cd> starts by validating all input files and planning the layout, then
+looking into I<work-directory> for any files not in the plan, exiting with an
+error if any are found.  If files exist that are in the plan, assume this is a
+rerun after crash and resume starting at the last file written.  E.g. if
+writing 100 files and crash after finishing file 98 but before starting file
+99, go ahead and rewrite file 98.
+
+=head2 Notes
+
+=over
+
+=item 1.
+Still considering restoring whole-disc FLAC file support.
+
+=item 2.
+Not yet implemented.
+
+=back
+
+=head1 OPTIONS
+
+=over
+
+=item B<-d> I<work-directory>
+
+Path of the directory to write the UDF layout into.  Default is the
+current directory.
+
+=item B<-o> I<image-file>
+
+Path of file to write UDF image to.  No default: if not specified, B<fa-mp3cd>
+prepares the I<work-directory> but makes no image.
+
+=item B<-V> I<VOLI>
+
+Passed through to B<mkisofs(1)>, which says:
+
+=item B<--no-resume>
+
+Assume files in I<work-directory> are up to date rather than assuming resume
+after crash.
+
+=item B<--print-size>
+
+Run B<mkisofs(1)> to print out the estimated size (in kilobytes) of the image
+as well as how much remains of the 650 MB allowed on CD-ROM.
+
+=over
+
+Specifies the volume ID (volume name or label) to be written into the master
+block.  There is space on the disc for 32 characters of information.
+
+=back
+
+Some systems may use this volume ID as the name of a mount point.
+
+=back
+
+=head1 AUTHORS
+
+Written by Eric Gillespie <epg@pretzelnet.org>.
+
+=cut
+
+package epg::flac::archive::mp3::cd;
+
+use v5.12;
+use warnings;
+use FindBin;
+
+# TODO  eval hack
+eval { require "$FindBin::Bin/tags.pl" } ;
+epg::flac::archive::tags->import(
+    qw[
+        disc_tags
+        read_tags
+        mangle_for_file_name
+        quote
+        two_digits
+    ]);
+
+# TODO dedup with flac2mp3
+sub track_tags {
+    my $tags = shift;
+    # TODO probably ought to return hash too.  then track_tags would augment
+    my ($artist, $album, $date, $discnumber) = disc_tags(%$tags);
+
+        # TODO resurrect whole-disc FLAC?
+        # Stupid hack: only a single-track file should have the
+        # TRACKNUMBER tag, so use it if set for the first pass through
+        # the loop.  At the end of the loop, we'll set $track for the
+        # next run, so this continues to work for multi-track files.
+                # if track == None:
+                #     track = 1
+                # else:
+                #     track = int(track)
+
+    my $tracknumber = epg::flac::archive::tags::one(TRACKNUMBER => $tags);
+    my $title = epg::flac::archive::tags::one(TITLE => $tags);
+    # TODO restore PARTNUMBER and VERSION next time i need them
+
+    (
+     artist => $artist,
+     album => $album,
+     date => $date,
+     discnumber => $discnumber,
+     tracknumber => $tracknumber,
+     title => $title,
+    )
+}
+
+sub plan_flac {
+    my $workdir = shift;
+    my $path = shift;
+    my $tags = shift;
+
+    my %track = track_tags($tags);
+    $track{flac} = $path;
+    $track{filename} = mangle_for_file_name(
+        join(' ',
+             (map { two_digits($_) } @{$track{discnumber}}),
+             two_digits($track{tracknumber}),
+             $track{title},
+        )) . '.mp3';
+    $track{dir} = join('/', $workdir, mangle_for_file_name($track{album}));
+    \%track
+}
+
+sub plan {
+    my $workdir = shift;
+    my @flac_data = @_;
+    my @layout;
+    for my $path_tags (@flac_data) {
+        push(@layout, plan_flac($workdir, @$path_tags));
+    }
+    @layout
+}
+
+sub check_directory {
+    my $workdir = shift;
+    my $workdir_files = shift;
+    my @layout = @_;
+
+    # if @layout is partially on disk, return the portion that remains to be written
+
+    my @remaining;
+    my $last;
+    my %workdir_files = map { ($_, 1) } @$workdir_files;
+    for my $mp3 (@layout) {
+        my $path = join('/', $mp3->{dir}, $mp3->{filename});
+        if (exists($workdir_files{$path})) {
+            delete($workdir_files{$path});
+            $last = $mp3;
+        } else {
+            if (defined($last)) {
+                push(@remaining, $last);
+                undef($last);
+            }
+            push(@remaining, $mp3);
+        }
+    }
+    defined($last) && push(@remaining, $last);
+    if (%workdir_files > 0) {
+        die("unexpected files in $workdir: ", join(' ', keys(%workdir_files)))
+    }
+
+    @remaining
+}
+
+sub read_args {
+    my $workdir = '.';          # TODO
+    my $imagefile;              # TODO
+    # TODO other flags
+    $workdir, $imagefile, @_
+}
+
+# TODO dup from flac2mp3 but move to tags.pl
+sub read_tags_metaflac {
+    my $fn = shift;
+    open(my $fh, '-|', 'metaflac', '--no-utf8-convert', '--export-tags-to=-', $fn) || die("metaflac: $!");
+    my @result = read_tags($fh);
+    if (!close($fh)) {
+        if ($! == 0) {
+            die("metaflac exited $?")
+        }
+        die("close(metaflac): $!")
+    }
+    @result
+}
+
+sub find_files {
+    my $path = shift;
+    # If we come across stupid file names embedded newlines, check_directory will complain about them.
+    open(my $fh, '-|', 'find', $path, '!', '-type', 'd') || die("find $path ! -type d: $!");
+    my @files;
+    while (<$fh>) {
+        chomp;
+        push(@files, $_);
+    }
+    if (!close($fh)) {
+        if ($! == 0) {
+            die("find $path ! -type d exited $?")
+        }
+        die("close(find $path ! -type d): $!")
+    }
+    \@files
+}
+
+# dedup with flac2mp3
+sub flac2mp3 {
+    my $mp3 = shift;
+    my $flac = shift;
+    my $tags = shift;
+    say(join(' ',
+             'flac',
+             '-cd',
+             quote($flac),
+             '|',
+             'lame',
+             '--id3v2-only',
+             '--id3v2-latin1',
+             '--pad-id3v2-size', 0,
+             '--preset standard',
+             '--ta',
+             quote($tags->{artist}),
+             '--tl',
+             quote($tags->{album}),
+             '--tn',
+             quote($tags->{tracknumber}),
+             '--tt',
+             quote($tags->{title}),
+             '--ty',
+             quote($tags->{date}),
+             '$pic_options',
+             #(map { ('--tv', quote("TPE2=$_")) } @{$albumartist}),
+             (map { ('--tv', quote("TPOS=$_")) } @{$tags->{discnumber}}),
+             '-',
+             quote($mp3),
+        ))
+}
+
+sub main {
+    my ($workdir, $imagefile, @flac_files) = read_args(@_);
+
+    # -no-resume would mean skipping preparing the work directory at all.
+    # So that's probably not the right name for the flag.
+    my @layout = check_directory(
+        $workdir,
+        find_files($workdir),
+        plan(
+            $workdir,
+            map { ["$_", read_tags_metaflac($_)] } @flac_files,
+        ));
+    for my $mp3 (@layout) {
+        say('mkdir -p ' . $mp3->{dir});
+        flac2mp3(join('/', $mp3->{dir}, $mp3->{filename}), $mp3->{flac}, $mp3);
+    }
+
+    my @output_args;
+    if (defined($imagefile)) {
+        # Have to redirect it; won't accept -o with -reproducible-date!
+        #'-o', quote($imagefile)
+        @output_args = ('>', quote($imagefile));
+    } else {
+        # "Print estimated filesystem size in multiples of the sector size (2048 bytes)"
+        # Overhead seems to be under 1MB so shoot for total of 649 MB of files.
+        @output_args = ('-print-size');
+    }
+    # If I cared for fully reproducible image, set all file and directory timestamps:
+    #  touch -d `git log -1 '--format=%cd' '--date=format:%FT%T'`
+    say(join(' ',
+             'mkisofs',
+             #'-reproducible-date', `date '+%Y-%m-%d %H:%M:%S %z'`,
+             #'-reproducible-date', `git log -1 '--format=%ai'`,
+             #'-reproducible-date', quote('2022-03-11 23:37:11 -0600'),
+             '-J',
+             '-full-iso9660-filenames',
+             '-r',
+             '-udf',
+             quote($workdir),
+             @output_args,
+        ));
+
+    0
+}
+
+if (!caller) {
+    exit(main(@ARGV))
+}
+
+1;