0) { $free = "free".zero($freesize); $free = fb($free).$free; $meta = $meta.$free; } $meta = fb($meta).$meta; $udta = "udta".$meta; $udta = fb($udta).$udta; return $udta; } // create udta // iTunes m4a header function m4ahdr() { return zero(3).chr(32)."ftypm4a".chr(32).zero(4)."M4A".chr(32)."mp4isom".zero(4); } // write iTunes m4a heading to beginning of file // take the moov atom starting at byte 32 and copy it to end // change original moov atom to free atom // change atomsize of new moov atom to accomodate new tags function MOOVtoEnd($f, $len, $tags) { if(!isset($tags['moovpos'])) return FALSE; $moovpos = $tags['moovpos']; fseek($f, $moovpos, SEEK_SET); $AtomHeader = fread($f, 8); $atomsize = BigEndian2Int(substr($AtomHeader, 0, 4)); $atomname = substr($AtomHeader, 4, 4); if($moovpos == 32) { fseek($f, 0, SEEK_SET); // move to beginning fwrite($f, m4ahdr()); // write a compliant header } fseek($f, $moovpos, SEEK_SET); // move to start of moov atom $moov = fread($f, $atomsize); // read entire moov atom fseek($f, $moovpos+4, SEEK_SET);// back to start of atom fwrite($f, "free"); // change moov atom to free fseek($f, 0, SEEK_END); // move to end of file fwrite($f, pack("N", $atomsize+$len)); // write the new size fwrite($f, substr($moov, 4)); // write the moov atom return TRUE; } function parsepath($r, &$tags) { $a = pathinfo($r); $file = basename($a['basename'], ".".$a['extension']); $b = explode("\\", $a['dirname']); $i = count($b); $album = $b[$i-1]; $artist = $b[$i-2]; $trk = 10*($file{0}-"0") + $file{1}-"0"; if(99 < $trk || $trk < 0) { $trk = 0; } $title = substr($file, 5); $tags['artist'] = $artist; $tags['album'] = $album; $tags['track'] = $trk; $tags['title'] = $title; } // id3v1 codes plus one function genrecode($gnre) { global $GenreList; for($i = 0; $i < count($GenreList); $i++) { if(strcasecmp($GenreList[$i], $gnre) == 0) { return $i+1; } } return 255; } function tagger($argc, $argv) { // global $debug; // get the name of the infile from the command line and make sure it exists $infile = $argv[1]; if(!is_file($infile)) { printf("File not found: %s\n", $infile); return FALSE; } $f = fopen($infile, "rb"); // read the existing tags ParseAtom($f, 0, filesize($infile), $tags); // if auto, read tag fields from pathname. // cmd line tags override pathname, which is why this is before general cmd line read $changetags = 0; for($i = 2; $i < $argc; $i++) { if(strcmp($argv[$i], "--auto") == 0) { $changetags = 2; parsepath(realpath($infile), &$tags); break; } } // read params from command line $back = 0; $remove = 0; for($i = 2; $i < $argc; $i++) { switch($argv[$i]) { case "--artist": $tags['artist'] = $argv[++$i]; $changetags = 1; break; case "--album": $tags['album'] = $argv[++$i]; $changetags = 1; break; case "--title": $tags['title'] = $argv[++$i]; $changetags = 1; break; case "--track": $tags['track'] = $argv[++$i]; $changetags = 1; break; case "--genre": $tags['genrename'] = $argv[++$i]; $tags['genrenum'] = genrecode($argv[$i]); $changetags = 1; break; case "--year": $tags['year'] = $argv[++$i]; $changetags = 1; break; case "--auto": $auto = 1; $changetags = 2; break; case "--back": $back = 1; break; case "--debug": $debug = 1; break; case "--remove"; $remove = 1; break; default: printf("unknown field: %s\n", $argv[$i]); return FALSE; } // switch } // for // if remove, do it if($remove) { if(!isset($tags['udtapos'][0])) printf("no existing tags\n"); else { foreach($tags['udtapos'] as $key=>$val) { // free existing tag fclose($f); $f = fopen($infile, 'r+b'); fseek($f, $val+4, SEEK_SET); fwrite($f, "free"); } // foreach fclose($f); // system("mp4creator60.exe -optimize \"$infile\""); $f = fopen($infile, 'rb'); unset($tags); ParseAtom($f, 0, filesize($infile), $tags); fclose($f); printf("\n%s\n", $infile); printtags($tags); } // else return TRUE; } // if remove // if we are not changing tags, just report tag info and return if($changetags == 0) { if($debug) { $num = count($tags['atoms']); for($i = 0; $i < $num; $i++) printf("%s%s (%s bytes at %s)\n", str_repeat('-',$tags['atoms'][$i]['level']-1), $tags['atoms'][$i]['name'], number_format($tags['atoms'][$i]['size']), number_format($tags['atoms'][$i]['pos']) ); } // if debug printf("\n%s\n", $infile); if(isset($tags)) { printtags($tags); } else { printf("no tags\n"); } fclose($f); return TRUE; } // no change // if --noback on cmdline, then don't create a new file to write // else write to oldfile.xyz if($back == 0) { $outfile = $infile; } else { $outfile = $infile.".xyz"; copy($infile, $outfile); } // do it $fdate = filemtime($outfile); // remember file time $f = fopen($outfile, 'r+b'); $udta = CreateUDTA($tags, 0); // create a udta atom from tags // is there an existing udta atom we can overwrite? $wrote = 0; if(isset($tags['udtapos'][0])) { for($i =0; $i < count($tags['udtapos']); $i++) { if((strlen($udta) < $tags['udtasize'][$i]) && !$wrote) { $udta = CreateUDTA($tags, $tags['udtasize'][$i]); fseek($f, $tags['udtapos'][$i], SEEK_SET); fwrite($f, $udta); $wrote = 1; } // if else { fseek($f, $tags['udtapos'][$i]+4, SEEK_SET); fwrite($f, "free"); } } // for } // if isset {check udta's) // is there a free atom under moov we can overwrite? $top = ""; $num = count($tags['atoms']); for($i = 0; $i < $num; $i++) { // loop through the atoms if(($tags['atoms'][$i]['level']-1) == 0) { // check top level atoms $top = $tags['atoms'][$i]['name']; continue; } if(strcmp($top, "moov") == 0) // to see if they are moov if(($tags['atoms'][$i]['level']-1) == 1) if(strcmp($tags['atoms'][$i]['name'], "free") == 0) { if((strlen($udta) < $tags['atoms'][$i]['size']) && !$wrote) { $udta = CreateUDTA($tags, $tags['atoms'][$i]['size']); fseek($f, $tags['atoms'][$i]['pos'], SEEK_SET); fwrite($f, $udta); $wrote = 1; break; } // found a place to write } // found a free } // if we can't overwrite something, moov to the end and write the tags after it if(!$wrote) { $udta = CreateUDTA($tags, 750); if (MOOVtoEnd($f, strlen($udta), $tags) == FALSE) { return FALSE; } fwrite($f, $udta); } fclose($f); // use mp4creator to clean up a bit if(!$wrote) { system("mp4creator60.exe -optimize \"$outfile\""); OptFree($infile, $tags); } // keep old file time touch($outfile, $fdate); // read the new tags $f = fopen($outfile, "rb"); unset($tags); ParseAtom($f, 0, filesize($outfile), $tags); printf("\n%s\n", $outfile); printtags($tags); fclose($f); } // main ------------------------------------------- // if($argc == 1) { print << Options Wildcards are supported in filename Options: [none] print existing tags --debug print existing tags and atom info --remove remove existing tags --auto parses filepath - must be in form "artist\album\\nn - title.ext" --artist --album --title --track --genre [id3v1 tags only] --year mp4creator must be available: http://rarewares.hydrogenaudio.org/files/mp4creator60.EP.zip thanks to www.getid3.org and tag for inspiration END; } $filenames = glob($argv[1]); // get a list of all filenames matching cmd line pattern if(count($filenames) == 0) { printf("File not found: %s\n", $argv[1]); exit; } $parms = $argv; foreach($filenames as $fname) { $parms[1] = realpath($fname); tagger($argc, $parms); } ?>