#!/usr/bin/perl
use warnings;
use strict 'subs';
package mp4;
exit &mp4batchnerf(@ARGV) unless caller();
################################################
# mp4nerf.pl
# c version by DJ Grenola on 17 July 2007
# perl version by SelbyMD on 20 September 2008
# license: none (public domain)
################################################
sub mp4batchnerf {
print "MP4 audio track disabler\n";
print "beta 0.2, DJ Grenola & SelbyMD\n\n";
print "This software comes with no warranty.\n\n";
unless (grep {-e} @_) {
print "Usage: mp4nerf.pl <MP4 file> [ <MP4 file> ... ] to print track information\n";
print "Usage: mp4nerf.pl -n <MP4 file> [ <MP4 file> ... ] to disable commentary\n";
print "Usage: mp4nerf.pl -u <MP4 file> [ <MP4 file> ... ] to re-enable commentary\n";
return 1;
}
$action = 'print';
for $arg (@_) {
if ($arg eq '-p') { $action = 'print' }
elsif ($arg eq '-u') { $action = 'unnerf' }
elsif ($arg eq '-n') { $action = 'nerf' }
elsif ($arg eq '-x') { $action = 'nerf' }
else { $files++; $errors += &mp4nerf($arg) }
}
if ($errors) {
printf "[-] %s file%s had errors during processing.\n",
($files == 1) ? ('The', '') : ("${errors} of ${files}", 's');
} else {
printf "[+] %s file%s processed successfully.\n",
($files == 1) ? ('The', ' was') : ("All ${files}", 's were');
}
return $errors;
}
sub mp4nerf {
$mp4file = shift;
$matches = 0;
$tracks_to_skip = ($action eq 'nerf') ? 1 : 0;
unless (open(FH, '+<:raw', $mp4file)) {
print qq([-] Failed to open file "${mp4file}".\n\n);
return 1;
}
print qq([+] Scanning file "${mp4file}".\n);
@traks = &getatomtree([qw(moov trak)], [0, -s $mp4file]);
unless (@traks) {
print qq([-] No tracks found. Maybe this isn't an mp4 file.\n\n);
close(FH);
return 1;
}
for $trak (@traks) {
($tkhd) = &getatoms('tkhd', @$trak);
($offset, $id, $status, $type) = ($$tkhd[0], &id($tkhd), &status($tkhd), &type($trak));
unless (defined($id) and defined($status)) {
print qq([-] Error reading from file "${mp4file}".\n\n);
close(FH);
return 1;
}
printf "[+] Track %d (%7s,%8s) at 0x%08x, ", $id, $type, $status, $$trak[0] - 8;
if ($type ne 'audio') {
print "ignored.\n";
} elsif (++$matches <= $tracks_to_skip) {
print "skipped.\n";
} elsif (($action eq 'nerf') and ($status eq 'enabled')) {
print "attempting to disable ...\n";
if (nerf($offset)) {
print "[+] Nerfed successfully.\n";
} else {
print "[-] Failed to nerf this track.\n\n";
close(FH);
return 1;
}
} elsif (($action eq 'unnerf') and ($status eq 'disabled')) {
print "attempting to enable ...\n";
if (unnerf($offset)) {
print "[+] Unnerfed successfully.\n";
} else {
print "[-] Failed to unnerf this track.\n\n";
close(FH);
return 1;
}
} else {
print "no action taken.\n";
}
}
print qq([+] File "$mp4file" processed successfully.\n\n);
close(FH);
return 0;
}
sub myread { sysseek(FH, $_[1], 0) and ( sysread(FH, $_[0], $_[2]) == $_[2]) }
sub mywrite { sysseek(FH, $_[1], 0) and (syswrite(FH, $_[0], $_[2]) == $_[2]) }
sub nerf { &myread($temp, $_[0] + 3, 1) and &mywrite(pack('C', unpack('C', $temp) & 0xFE), $_[0] + 3, 1) }
sub unnerf { &myread($temp, $_[0] + 3, 1) and &mywrite(pack('C', unpack('C', $temp) | 0x01), $_[0] + 3, 1) }
sub isvideo { &getatomtree([qw(mdia minf vmhd)], $_[0]) }
sub isaudio { &getatomtree([qw(mdia minf smhd)], $_[0]) }
sub type { &isaudio ? 'audio' : &isvideo ? 'video' : 'unknown' }
sub id { &myread($temp, $_[0][0] + 12, 4) ? unpack('N', $temp) : 0 }
sub status { &myread($temp, $_[0][0] + 3, 1) ? ((unpack('C', $temp) % 2) ? 'enabled' : 'disabled') : undef }
sub getatoms {
($atomname, $start, $end) = @_;
@atoms = ();
use bytes; # needed for substr here
while ($start < $end) {
last unless &myread($temp, $start, 8);
($size, $name) = (unpack('N', substr($temp, 0, 4)), substr($temp, 4, 4));
if ($size == 1) { # might not have 'Q', so do this manually
last unless &myread($temp, $start + 8, 8);
$size = unpack('N', substr($temp, 0, 4)) * 2**32 + unpack('N', substr($temp, 4, 4));
}
if ($size == 0) { $size = (-s $mp4file) - $start; }
if ($name eq $atomname) { push @atoms, [$start + 8, $start + $size]; }
$start += $size;
}
@atoms;
}
sub getatomtree {
@tree = ($_[1]);
for my $atomname (@{$_[0]}) {
last unless @tree;
@tree = map { &getatoms($atomname, @$_) } @tree;
}
@tree;
}
1;