pastebin - collaborative debugging tool
kpaste.net RSS


flac2lame perl script threaded
Posted by Anonymous on Wed 12th Sep 2012 10:50
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. 4 threads of 'lame'
  21. # are started and left waiting for files to be enqueued.
  22.  
  23. use 5.14.0;
  24. use strict;
  25. use warnings;
  26. use Cwd qw(abs_path);
  27. use File::Path qw(make_path);
  28. use File::Basename qw(basename);
  29.  
  30. use threads qw(yield);
  31. use threads::shared;
  32. use Thread::Queue;
  33.  
  34. my $total :shared = 0;
  35. my $disk_size = 1000000000;
  36.  
  37. my @dirs;
  38. my %pcm :shared;
  39. my @opts = ('-q 0', '-V 2', '--silent', '--id3v2-only');
  40. my $q = Thread::Queue->new();
  41. chomp(my $cpu_count = `grep -c "^processor" /proc/cpuinfo`);
  42.  
  43. if (!$ARGV[0] || ! -d $ARGV[0]) {
  44.         say "Usage: " . basename($0) .
  45.         " [FLAC directory 1] .. [FLAC directory n]";
  46.         exit;
  47. }
  48.  
  49. foreach my $arg (@ARGV) {
  50.         my $src = abs_path($arg);
  51.         if (-d $src) {
  52.                 getdirs($src);
  53.         }
  54. }
  55.  
  56. sub gettags {
  57.         my $fn = shift;
  58.         my (%t, @mflac);
  59.         open(OUTPUT, '-|',
  60.         qq{metaflac --no-utf8-convert --export-tags-to=- "$fn"})
  61.         or die "can't run metaflac: $!";
  62.  
  63.         chomp(@mflac = (<OUTPUT>));
  64.         foreach (@mflac) {
  65.                 my @tag = split('=');
  66.                 my $tagname = lc($tag[0]) or say $fn;
  67.  
  68.                 if ($t{$tagname}) { next; }
  69.                 elsif ($tag[1]) {
  70.                         $t{$tagname} = quotemeta($tag[1]);
  71.                 } else {
  72.                         $t{$tagname} = 'null';
  73.                 }
  74.         }
  75.         close(OUTPUT) || die "couldn't close metaflac: $!";
  76.         return %t;
  77. }
  78.  
  79. sub getdirs {
  80.  
  81.         my $dn = shift;
  82.  
  83.         local $/;
  84.         open(FIND, '-|', qq{find -L "$dn" -name "*" -type d})
  85.         or die "Can't run 'find': $!";
  86.         push(@dirs, (<FIND>));
  87.         chomp(@dirs);
  88.         close(FIND) or die "Can't close 'find': $!";
  89. }
  90.  
  91. sub getfiles {
  92.         my $dn = shift;
  93.         my @files;
  94.         opendir(my $dh, $dn) or die "Can't open directory '$dn': $!";
  95.         foreach (sort(readdir $dh)) {
  96.                 my $fn = "$dn/$_";
  97.                 if (/.flac$/ && -f $fn) {
  98.                         push(@files, $fn);
  99.                 }
  100.         }
  101.         closedir $dh or die "Can't close directory '$dn': $!";
  102.         return @files;
  103. }
  104.  
  105. sub name {
  106.  
  107.         my $fn = shift;
  108.         my %t = gettags($fn);
  109.         my $tref = \%t;
  110.         my $dest;
  111.  
  112.         checktags($fn, $tref);
  113.  
  114.         my %ct = ( albumartist => $t{albumartist}, album => $t{album},
  115.         discnumber => $t{discnumber}, tracknumber => $t{tracknumber},
  116.         title => $t{title} );
  117.  
  118.         foreach my $tag (keys(%ct)) {
  119.                 $ct{$tag} =~ s/[[:punct:]]//g;
  120.         }
  121.  
  122.         my @dest = ('home', $ENV{USER}, 'lame', $ct{albumartist},
  123.         $ct{album});
  124.         { local $" = '/'; $dest = "/@{dest}/"; }
  125.  
  126.         #system('mkdir', '-p', $dest);
  127.         unless (-d $dest) {
  128.                 make_path($dest) or die "Can't make_path '$dest': $!";
  129.         }
  130.  
  131.         my $newfn = sprintf('%s-%02s. %s.mp3', $ct{discnumber},
  132.         $ct{tracknumber}, $ct{title});
  133.         $newfn = $dest . $newfn;
  134.  
  135.         return($newfn, $tref);
  136. }
  137.  
  138. sub decode {
  139.  
  140.         my $fn = shift;
  141.         local $/;
  142.         open(FLAC, '-|:raw', qq{flac --silent --stdout --decode "$fn"})
  143.         or die "can't run 'flac': $!";
  144.         chomp($pcm{$fn} = <FLAC>);
  145.         close(FLAC) || die "couldn't close 'flac': $!";
  146.  
  147.         lock($total);
  148.         $total += length($pcm{$fn});
  149. }
  150.  
  151. sub checktags {
  152.         my $fn = shift;
  153.         my $tref = shift;
  154.         my %t = %{$tref};
  155.         foreach my $tag ('discnumber', 'albumartist', 'album',
  156.         'tracknumber', 'title') {
  157.                 if (! $t{$tag}) {
  158.                         say "${fn}: missing '$tag' tag.";
  159.                         exit;
  160.                 }
  161.         }
  162. }
  163.  
  164. say "Starting threads";
  165. my @threads;
  166. for my $i (1 .. $cpu_count) {
  167.         push(@threads, threads->create(\&lame));
  168. }
  169.  
  170. sub lame {
  171.  
  172.         my $started = 0;
  173.  
  174.         while ($q->pending() == 0 && $started == 0) {
  175.                 yield();
  176.         }
  177.  
  178.         $started++;
  179.  
  180.         my $tid = threads->tid();
  181.         my @old_opts = @opts;
  182.  
  183.         while (my $fn = $q->dequeue_nb()) {
  184.  
  185.                 my $oldfn = $fn;
  186.                 my ($newfn, $tref) = name($fn);
  187.                 my %t = %{$tref};
  188.  
  189.                 foreach my $tag ('artist', 'title', 'album', 'tracknumber',
  190.                 'date') {
  191.                         if ($t{$tag}) {
  192.                                 given($tag) {
  193.                                         when ('artist') {
  194.                                                 push(@opts, '--ta');
  195.                                                 push(@opts, $t{artist});
  196.                                         }
  197.                                         when ('title') {
  198.                                                 push(@opts, '--tt');
  199.                                                 push(@opts, $t{title});
  200.                                         }
  201.                                         when ('album') {
  202.                                                 push(@opts, '--tl');
  203.                                                 push(@opts, $t{album});
  204.                                         }
  205.                                         when ('tracknumber') {
  206.                                                 push(@opts, '--tn');
  207.                                                 push(@opts, $t{tracknumber});
  208.                                         }
  209.                                         when ('date') {
  210.                                                 $t{date} =~ /[0-9]{4}/;
  211.                                                 push(@opts, '--ty');
  212.                                                 push(@opts, $t{date});
  213.                                         }
  214.                                 }
  215.                         }
  216.                 }
  217.  
  218.                 say "thread${tid} ${newfn}: encoding...";
  219.                 open(LAME, '|-:raw', qq{lame @opts - "$newfn"})
  220.                 or die "can't run 'lame': $!";
  221.                 print LAME $pcm{$oldfn};
  222.                 close(LAME) || die "couldn't close 'lame': $!";
  223.  
  224.                 @opts = @old_opts;
  225.                 lock(%pcm);
  226.                 lock($total);
  227.                 $total -= length($pcm{$oldfn});
  228.                 delete($pcm{$oldfn});
  229.         }
  230. }
  231.  
  232. foreach my $dn (@dirs) {
  233.  
  234.         my @files = getfiles($dn);
  235.  
  236.         my @stat;
  237.         my $album_size = 0;
  238.  
  239.         if (scalar(@files) == 0) {
  240.                 next;
  241.         }
  242.  
  243.         # Check the size of the album to make more informed
  244.         # decisions on when to yield in the next loop
  245.         foreach my $fn (@files) {
  246.                 @stat = stat($fn);
  247.                 $album_size += $stat[7];
  248.         }
  249.  
  250.         foreach my $fn (@files) {
  251.  
  252.                 if ($album_size > 600000000) { goto(DECODE); }
  253.  
  254.                 until ($total < $disk_size) {
  255.                         yield();
  256.                 }
  257.  
  258.                 DECODE: decode($fn);
  259.         }
  260.  
  261.         #say "Enqueueing files";
  262.         foreach my $fn (@files) {
  263.                 $q->enqueue($fn);
  264.         }
  265.  
  266. }
  267.  
  268. foreach my $t (threads->list()) { $t->join(); }
  269. 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