#!/usr/bin/perl # This script is PDS; Public Domain Software. require 5.008; use strict; use warnings; binmode STDOUT, ":utf8"; binmode STDERR, ":utf8"; binmode STDIN, ":utf8"; if (@ARGV == 0) { print "Usage: vorbis-batch-comment \n"; exit 1; } &main; exit; sub main { my $fh_meta = do { if ($ARGV[0] eq '-') { # STDINから讀む。 \*STDIN; } else { my $fh; open $fh, '<:utf8', $ARGV[0] or die "failed to open $ARGV[0]\n"; $fh; } }; my ($universal, $eachfile) = &parse_meta($fh_meta); foreach my $each (@$eachfile) { my $fname_head = $each->[0]; my $tags = $each->[1]; # このファイル名パターンにマッチするファイルは存在するか? my @matched_files = &find_files_with_head($fname_head); if (@matched_files == 0) { warn "no files matched for [$fname_head]. skip...\n"; } elsif (@matched_files > 1) { warn "multiple files matched for [$fname_head]. skip...\n". join(', ', map {"[$_]"} @matched_files)."\n"; } else { my $target = $matched_files[0]; print "setting tags for [$target]...\n"; my $combined = {}; @$combined{keys %$universal} = values %$universal; @$combined{keys %$tags} = values %$tags; # 一時ファイルを作る。 my $tmpfile = "/tmp/vbc.$$"; open my $tmpfh, '>:utf8', $tmpfile or die "failed to make temporary file [$tmpfile].\n"; while (my ($key, $val) = each %$combined) { print $tmpfh "$key=$val\n"; } undef $tmpfh; eval { &get_processor($target)->($target, $tmpfile); }; if ($@) { warn "$@"; } unlink $tmpfile; } } } sub parse_meta { my $fh = shift; my $r_key = qr/[^;=]+/; my $r_value = qr/[^;]+/; my $r_pair = qr/$r_key\s*=\s*$r_value/; my $r_pairs = qr/$r_pair(?:\s*;\s*$r_pair)*/; my $r_each = qr/^(.+?)\s*;\s*($r_pairs)$/; my $univ = {}; my @files; foreach (<$fh>) { s/^\s*|\s*$//g; next if /^#/; # コメント next if length == 0; if (/$r_each/) { my $file = $1; my $tags = {}; foreach my $pair (split /($r_pair)/, $2) { next if !defined $pair; next if $pair !~ /($r_key)\s*=\s*($r_value)/; my ($key, $val) = ($1, $2); $key =~ s/^\s*|\s*$//g; $val =~ s/^\s*|\s*$//g; warn "warning: key of tag [$pair] has [^A-Z] char.\n" if $key =~ /[^A-Z]/; $tags->{$key} = $val; } if ($file eq '*') { # 共通 @$univ{keys %$tags} = values %$tags; } else { push @files, [$file, $tags]; } } else { die "parse error: $_\n"; } } ($univ, \@files); } sub find_files_with_head { my $head = shift; my @matched; opendir my $dh, '.'; while (defined($_ = readdir $dh)) { if (-f && /^$head/ && defined &get_processor($_)) { push @matched, $_; } } @matched; } sub get_processor { my $fname = shift; my $processors = { ogg => \&proc_ogg, flac => \&proc_flac, }; if ($fname =~ /\.([^\.]+)$/) { $processors->{$1}; } else { undef; } } sub proc_ogg { my ($fname, $tagfile) = @_; system 'vorbiscomment', '-w', $fname, '--raw', '-c', $tagfile and die "failed to use vorbiscomment: $!\n"; } sub proc_flac { my ($fname, $tagfile) = @_; system 'metaflac', $fname, '--remove-vc-all', "--import-vc-from=$tagfile", '--no-utf8-convert', '--dont-use-padding' and die "failed to use metaflac: $!\n"; }