Posted tagged ‘mp3’

MP3-Listing

24. Februar 2009

In the internal of my ensemble NiederrheinBrass we host the records of the last concerts. But I am lazy – why should I update that page each time I upload a new MP3? All required information are online: the URL of the file, title+record date as ID3 tags. And we have PHP…

So first a note: I am not a PHP programmer. I usually write in Java. So maybe these codings are not the best from a PHP view point. But that works …

I use the free Mphp3 script. But I just have seen that this not maintained any more… But you could the principles with another Read-ID3-Tag-Infos-PHP-Skript.

<?php
    // Einbinden der MP3-Funktionalität
    include('mphp3.php');
?>

Generating the content is easy – just

<?php
    // Einbinden der MP3-Funktionalität
    include('mphp3.php');

    $daten = new Daten();
    $daten->run();
?>

Ok – two things here: constructor and run() 😉

I don’t have implemented a constructor. The run() method asks a factory for a Generator and then that generator for the HTML. I use the factory and different generators because I not only need the MP3 listing …

<?php
    // include MP3 functions
    include('mphp3.php');

    $daten = new Daten();
    $daten->run();

    //==============================================================================================        

    /**
     * Create HTML listing.
     */
    class Daten {
        /** Start-Funktion */
        public function run() {
            $generator = $this->createGenerator();
            $generator->ausgabe();
        }

        /**
         * Factory
         * @return generator
         */
        private function createGenerator() {
            if ($_GET['art'] == "aufnahmen") {
                return new Aufnahmen();
            }
            if ($_GET['art'] == "bandstuecke") {
                $rv = new Aufnahmen();
                $rv->art = "bandstuecke";
                return $rv;
            }
            if ($_GET['art'] == "noten") {
                return new Noten();
            }
            return new Fehler();
        }
    }
?>

So the the URL …./index.php?aufnahmen starts my MP3-Generator.

For the generator I use an (abstract) class and four concrete classes. The parent class is not really abstract. It is just not instantiated by the factory. The parent class contains four fields for

  1. what should be generated? (aufnahmen should generate the MP3)
  2. what is the HTML file? (caching the work)
  3. what subdirectories should be scanned?
  4. what is the working directory for the generator?
    /**
     * Baseclass for Generators.
     */
    class Generator {
        var $art;
        var $htmlFile;
        var $dirs = array();
        var $curDir;

        /**
         * Generates HTML.
         */
        public function ausgabe() {
            $this->htmlFile = $this->art . ".html";
            // Caching
            if ($this->needsGeneration()) {
                // Generate
                // Activate buffer and write into that.
                ob_start();
                $this->generate();
                // Write buffer to file
                $html = ob_get_clean();
                $fd = fopen($this->htmlFile, "w");
                fwrite($fd, $html);
                fclose($fd);
            }
            // Just/finally pass file content to client
            readfile($this->htmlFile);
        }

        /**
         * Checks timestamp of the html file against all timestamps of the files.
         * @return true if there a new files
         */
        protected function needsGeneration() {
            if (!file_exists($this->htmlFile)) {
                # No html file, generation required
                return true;
            } else {
                $mtimeHtml = filemtime($this->htmlFile);
                $mtimeDir  = $this->getMTime($this->art);
                if ($mtimeHtml < $mtimeDir) {
                    # HTML too old, new generation required
                    return true;
                } else {
                    # no new content, no generation required
                    return false;
                }
                return $mtimeHtml < $mtimeDir;
            }
        }
&#91;/sourcecode&#93;

The easiest generator is the error generator. The factory creates that if there is an invalid URL argument. It prints an error message. It works as NullObject so I don't need to check all arguments.

&#91;sourcecode language='php'&#93;
    /**
     * "Generator" for error text.
     * @see NullObject Design Pattern
     */
    class Fehler extends Generator {
        // Überschreibe ausgabe() anstelle generate(), um das Caching zu deaktivieren.
        function ausgabe() {
            echo "Fehlerhafte Auswahl. Eventuell ist der Link falsch.";
        }
    }
&#91;/sourcecode&#93;

Before I show the implementation of the MP3-Generator, I show the directory layout used:
<ul>
	<li>Under the index.php (with the generator) there is one directory for each concrete generator. So here <em>aufnahmen</em>.</li>
	<li>The directory layout for each generator could be different. For this generator it contains directories with the date of the concert (yyyyMMdd).</li>
	<li>Each concert directory could contain an ort.inc with the place where we played. I use its content as header.</li>
	<li>Each concert directory could (should) contain *.mp3 files with ID3v2 tags.</li>
</ul>

    class Generator {
        /**
         * Generation. Subclasses should overwrite this.
         */
        protected function generate() {
            $this->dirs = $this->getDirs();
            $firstOpenElement = $this->dirs[0];

            $this->htmlHeader();
            $this->htmlBodyStart($firstOpenElement);
            $this->htmlNavigation();

            echo "
<div id=\"Inhalt\">\n";
            foreach($this->dirs as $dir) {
                $this->curDir = $this->art . "/$dir";
                $titel = $this->getTitel($dir);
                echo "
<div class=\"hideable\" id=\"id$dir\">\n";
                echo "
<h2>$titel</h2>
\n";
                @readfile($this->curDir . "/header.inc");
                echo "
<table border=\"1\" cellspacing=\"0\" cellpadding=\"4\">\n";
                $this->htmlInhaltUeberschrift();
                $files = $this->getFiles($this->curDir);
                foreach($files as $file) {
                    $this->htmlInhaltZeile("$this->curDir/$file", $file);
                }
                echo "</table>
\n";
                echo "</div>
\n";
            }
            echo "</div>
\n\n";

            $this->htmlFooter();
        }
    }

 //==============================================================================================        

    /**
     * Generator for MP3-Listing.
     */
    class Aufnahmen extends Generator {
        /** Construktor. */
        public function Aufnahmen() {
            $this->art = "aufnahmen";
        }

        /*
         * The newest record on top.
         * @Override
         */
        protected function sortDirs($array) {
            $rv = $array;
            sort($rv);
            return array_reverse($rv);
        }

        /* @Override */
        protected function getTitel($dir) {
            $aufnahmeDatum = ereg_replace("(....)(..)(..)", "\\3.\\2.\\1", $dir);
            $incFile = $this->art . "/$dir/ort.inc";
            if (file_exists($incFile)) {
                return $aufnahmeDatum . " " . file_get_contents($incFile);
            } else {
                return $aufnahmeDatum;
            }
        }

        /* @Override */
        protected function htmlInhaltUeberschrift() {
            echo "
<tr>
<th>Name</th>
<th>Interpret</th>
<th>Dauer</th>
<th>Größe</th>
</tr>
\n";
        }

        /* @Override */
        protected function htmlInhaltZeile($file, $filename) {
            ...
        }
    }

The generate() method of the abstract class generates the common HTML layout. Subclasses should overwrite only special methods and hook there content into the common layout. I dont want to write over the layout, the CSS for that or the javascript used there.

The real content should be in a <div> section with the id Inhalt. For each concert directory (subdirectory of the generator) a <div> with the titel and content is placed. The content is a HTML table contained of a header row and a data row for each file. The methods used are htmlInhaltUeberschrift() for the table header row and htmlInhaltZeile() for the data rows.

The header creates columns for name, actor, time and filesize of the file.

/* @Override */
protected function htmlInhaltZeile($file, $filename) {
    if (!$this->endsWith($filename, ".inc")) {
        echo "
<tr>";
        if ($this->endsWith($filename, ".mp3")) {
            $info = new mphp3();
            $info->load($file);
            if ($info->valid) {
                $titel   = ($info->v2_title)  ? $info->v2_title  : $info->v1_title;
                $artist  = ($info->v2_artist) ? $info->v2_artist : $info->v1_artist;
                $dauer   = $info->lengthstring;
                $anzeige = ($titel) ? $titel : $filename;
                echo "    ";
                echo "
<td><a href=\"$file\">$anzeige</a></td>
";
                echo "
<td>$artist</td>
";
                echo "
<td>$dauer min</td>
";
            }
            $size = round(filesize($file) / 1024 / 1024, 1);
            echo "
<td>$size MB</td>
";
        } else {
            echo "
<td colspan=\"3\"><a href=\"$file\">$filename</a></td>
";
        }
        echo "</tr>
\n";
    } else {
        // Keine Ausgabe der *.inc Dateien
    }
}
}

Basically this method

  • ignores *.inc files
  • for *.mp3
    • instantiate the MP3 reader
    • read title, artist and length via that reader (id3v2 if possible, id3v1 else)
    • read the filesize in MB
  • create a data row
Werbung