pastebin - collaborative debugging tool
kpaste.net RSS


lame2flac perl script without use of external shells
Posted by Anonymous on Tue 25th Sep 2012 20:47
raw | new post

  1. #!/usr/bin/perl
  2.  
  3. # Copyright © 2012 Adam Nilsson
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17.  
  18. # This script decodes FLAC files into RAM up to the limit defined in
  19. # $disk_size. The $total variable is used to count the total amount
  20. # of data decoded and stored in RAM at any time.
  21.  
  22. # The script looks for the CPU core count in /proc/cpuinfo and starts
  23. # as many threads of 'lame' as it finds cores. The threads are left
  24. # waiting for files to be enqueued.
  25.  
  26. use 5.14.0;
  27. use strict;
  28. use warnings;
  29. use Cwd qw(abs_path);
  30. use File::Path qw(make_path);
  31. use File::Basename qw(basename);
  32.  
  33. use threads qw(yield);
  34. use threads::shared;
  35. use Thread::Queue;
  36.  
  37. my $total :shared = 0;
  38. my $disk_size = 1000000000;
  39. my $end :shared;
  40. my %decoded :shared;
  41.  
  42. my @dirs;
  43. my %pcm :shared;
  44. my @opts = ('-q 0', '-V 2', '--silent', '--id3v2-only');
  45. my $q = Thread::Queue->new();
  46. chomp(my $cpu_count = `grep -c "^processor" /proc/cpuinfo`);
  47.  
  48. if (!$ARGV[0] || ! -d $ARGV[0]) {
  49.         say "Usage: " . basename($0) .
  50.         " [FLAC directory 1] .. [FLAC directory n]";
  51.         exit;
  52. }
  53.  
  54. foreach my $arg (@ARGV) {
  55.         my $src = abs_path($arg);
  56.         if (-d $src) {
  57.                 getdirs($src);
  58.         }
  59. }
  60.  
  61. sub gettags {
  62.         my $fn = shift;
  63.         my (%t, @mflac);
  64.         open(OUTPUT, '-|', 'metaflac', '--no-utf8-convert',
  65.         '--export-tags-to=-', $fn) or die "can't run metaflac: $!";
  66.  
  67.         chomp(@mflac = (<OUTPUT>));
  68.         foreach (@mflac) {
  69.                 my @tag = split('=');
  70.                 my $tagname = lc($tag[0]) or say $fn;
  71.  
  72.                 if ($t{$tagname}) { next; }
  73.                 elsif ($tag[1]) {
  74.                         $t{$tagname} = quotemeta($tag[1]); }
  75.                 else { $t{$tagname} = 'null'; }
  76.         }
  77.         close(OUTPUT) || die "couldn't close metaflac: $!";
  78.         return %t;
  79. }
  80.  
  81. sub getdirs {
  82.  
  83.         my $dn = shift;
  84.  
  85.         open(FIND, '-|', 'find', $dn, '-name', '*', '-type', 'd')
  86.         or die "Can't run 'find': $!";
  87.         push(@dirs, (<FIND>));
  88.         chomp(@dirs);
  89.         close(FIND) or die "Can't close 'find': $!";
  90. }
  91.  
  92. sub getfiles {
  93.         my $dn = shift;
  94.         my @files;
  95.         opendir(my $dh, $dn) or die "Can't open directory '$dn': $!";
  96.         foreach (readdir $dh) {
  97.                 my $fn = "$dn/$_";
  98.                 if (/.flac$/ && -f $fn) {
  99.                         push(@files, $fn);
  100.                 }
  101.         }
  102.         closedir $dh or die "Can't close directory '$dn': $!";
  103.         return @files;
  104. }
  105.  
  106. sub name {
  107.  
  108.         my $fn = shift;
  109.         my %t = gettags($fn);
  110.         my $tref = \%t;
  111.         my $dest;
  112.  
  113.         checktags($fn, $tref);
  114.  
  115.         my %ct = ( albumartist => $t{albumartist}, album => $t{album},
  116.         discnumber => $t{discnumber}, tracknumber => $t{tracknumber},
  117.         title => $t{title} );
  118.  
  119.         foreach my $tag (keys(%ct)) {
  120.                 $ct{$tag} =~ s/[[:punct:]]//g;
  121.         }
  122.  
  123.         my @dest = ($ENV{HOME}, 'lame', $ct{albumartist},
  124.         $ct{album});
  125.         { local $" = '/'; $dest = "@{dest}/"; }
  126.  
  127.         unless (-d $dest) {
  128.                 make_path($dest) or warn "Can't make_path '$dest': $!";
  129.                 #system('mkdir', '-p', $dest) or warn "Can't run 'mkdir': $!";
  130.         }
  131.  
  132.         my $newfn = sprintf('%s-%02s. %s.mp3', $ct{discnumber},
  133.         $ct{tracknumber}, $ct{title});
  134.         $newfn = $dest . $newfn;
  135.  
  136.         return($newfn, $tref);
  137. }
  138.  
  139. sub decode {
  140.  
  141.         my $fn = shift;
  142.         local $/; # Enable slurp mode.
  143.         open(FLAC, '-|:raw', 'flac', '--silent', '--stdout',
  144.         '--decode', $fn) or die "can't run 'flac': $!";
  145.         chomp($pcm{$fn} = (<FLAC>));
  146.         close(FLAC) || die "couldn't close 'flac': $!";
  147.         lock(%decoded);
  148.         lock($total);
  149.         $decoded{$fn} = 1;
  150.         $total += length($pcm{$fn});
  151. }
  152.  
  153. sub checktags {
  154.         my $fn = shift;
  155.         my $tref = shift;
  156.         my %t = %{$tref};
  157.         foreach my $tag ('discnumber', 'albumartist', 'album',
  158.         'tracknumber', 'title') {
  159.                 if (! $t{$tag}) {
  160.                         say "${fn}: missing '$tag' tag.";
  161.                         exit;
  162.                 }
  163.         }
  164. }
  165.  
  166. say "Starting threads";
  167. my @threads;
  168. foreach (1 .. $cpu_count) {
  169.         push(@threads, threads->create(\&lame));
  170. }
  171.  
  172. sub lame {
  173.  
  174.         while (!$q->pending() && !$end) {
  175.                 yield();
  176.         }
  177.  
  178.         my $tid = threads->tid();
  179.         my @old_opts = @opts;
  180.  
  181.         LOOP: while (my $fn = $q->dequeue_nb()) {
  182.  
  183.                 my $oldfn = $fn;
  184.                 my ($newfn, $tref) = name($fn);
  185.                 my %t = %{$tref};
  186.  
  187.                 foreach my $tag ('artist', 'title', 'album', 'tracknumber',
  188.                 'date') {
  189.                         if ($t{$tag}) {
  190.                                 given($tag) {
  191.                                         when ('artist') {
  192.                                                 push(@opts, ('--ta', $t{artist}));
  193.                                         }
  194.                                         when ('title') {
  195.                                                 push(@opts, ('--tt', $t{title}));
  196.                                         }
  197.                                         when ('album') {
  198.                                                 push(@opts, ('--tl', $t{album}));
  199.                                         }
  200.                                         when ('tracknumber') {
  201.                                                 push(@opts, ('--tn', $t{tracknumber}));
  202.                                         }
  203.                                         when ('date') {
  204.                                                 $t{date} =~ /[0-9]{4}/;
  205.                                                 push(@opts, ('--ty', $t{date}));
  206.                                         }
  207.                                 }
  208.                         }
  209.                 }
  210.  
  211.                 say "thread${tid} ${newfn}: encoding...";
  212.                 open(LAME, '|-:raw', 'lame', @opts, '-', $newfn)
  213.                 or die "can't run 'lame': $!";
  214.                 print LAME $pcm{$oldfn};
  215.                 close(LAME) || die "couldn't close 'lame': $!";
  216.  
  217.                 @opts = @old_opts;
  218.                 lock(%pcm);
  219.                 lock($total);
  220.                 $total -= length($pcm{$oldfn});
  221.                 delete($pcm{$oldfn});
  222.         }
  223.        
  224.         if (!$q->pending() && !$end) {
  225.                 yield();
  226.                 goto(LOOP);
  227.         }
  228. }
  229.  
  230. foreach my $dn (sort(@dirs)) {
  231.  
  232.         my @files = getfiles($dn);
  233.  
  234.         if (!@files) {
  235.                 next;
  236.         }
  237.  
  238.         foreach my $fn (@files) {
  239.  
  240.                 decode($fn);
  241.  
  242.                 # If the RAM disk is full, put decoded files in the queue,
  243.                 # and then yield until the LAME threads have cleared some RAM.
  244.  
  245.                 while ($total > $disk_size) {
  246.                         foreach my $fn (keys(%decoded)) {
  247.                                 $q->enqueue($fn);
  248.                                 lock(%decoded);
  249.                                 delete($decoded{$fn});
  250.                         }
  251.                         yield();
  252.                 }
  253.         }
  254.        
  255.         foreach my $fn (keys(%decoded)) {
  256.                 $q->enqueue($fn);
  257.                 lock(%decoded);
  258.                 delete($decoded{$fn});
  259.         }
  260. }
  261.  
  262. $end = 1;
  263.  
  264. foreach my $t (threads->list()) { $t->join(); }
  265. say "All threads joined";

Submit a correction or amendment below (click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:

To highlight particular lines, prefix each line with {%HIGHLIGHT}




All content is user-submitted.
The administrators of this site (kpaste.net) are not responsible for their content.
Abuse reports should be emailed to us at