From d8461c6c855c4984e0acf706109c73263657a22e Mon Sep 17 00:00:00 2001 From: Eric Gillespie Date: Mon, 10 Oct 2022 19:09:46 -0500 Subject: [PATCH] Mar 18 2022 --- fa-mp3cd.tested | 311 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100755 fa-mp3cd.tested diff --git a/fa-mp3cd.tested b/fa-mp3cd.tested new file mode 100755 index 0000000..81568f9 --- /dev/null +++ b/fa-mp3cd.tested @@ -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 - Make Universal Disk Format image of MP3 files from FLAC files + +=head1 SYNOPSIS + +B [B<-d> I] [B<-o> I] [B<-V> I] I [...] + +=head1 DESCRIPTION + +Each I 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 starts by validating all input files and planning the layout, then +looking into I 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 + +Path of the directory to write the UDF layout into. Default is the +current directory. + +=item B<-o> I + +Path of file to write UDF image to. No default: if not specified, B +prepares the I but makes no image. + +=item B<-V> I + +Passed through to B, which says: + +=item B<--no-resume> + +Assume files in I are up to date rather than assuming resume +after crash. + +=item B<--print-size> + +Run B 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 . + +=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; -- 2.56.0