mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-25 06:27:59 +01:00
Lots of playlist things
This commit is contained in:
parent
e2bb57922a
commit
2a8bd3c85c
126 changed files with 36910 additions and 217 deletions
|
@ -3,7 +3,9 @@
|
||||||
namespace Api\Web;
|
namespace Api\Web;
|
||||||
|
|
||||||
use Commands\CreateAlbumCommand;
|
use Commands\CreateAlbumCommand;
|
||||||
|
use Commands\DeleteAlbumCommand;
|
||||||
use Commands\DeleteTrackCommand;
|
use Commands\DeleteTrackCommand;
|
||||||
|
use Commands\EditAlbumCommand;
|
||||||
use Commands\EditTrackCommand;
|
use Commands\EditTrackCommand;
|
||||||
use Cover;
|
use Cover;
|
||||||
use Entities\Album;
|
use Entities\Album;
|
||||||
|
@ -14,8 +16,20 @@
|
||||||
use Illuminate\Support\Facades\Response;
|
use Illuminate\Support\Facades\Response;
|
||||||
|
|
||||||
class AlbumsController extends \ApiControllerBase {
|
class AlbumsController extends \ApiControllerBase {
|
||||||
|
public function postCreate() {
|
||||||
|
return $this->execute(new CreateAlbumCommand(Input::all()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postEdit($id) {
|
||||||
|
return $this->execute(new EditAlbumCommand($id, Input::all()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postDelete($id) {
|
||||||
|
return $this->execute(new DeleteAlbumCommand($id));
|
||||||
|
}
|
||||||
|
|
||||||
public function getOwned() {
|
public function getOwned() {
|
||||||
$query = Album::summary()->where('user_id', \Auth::user()->id)->get();
|
$query = Album::summary()->where('user_id', \Auth::user()->id)->orderBy('created_at', 'desc')->get();
|
||||||
$albums = [];
|
$albums = [];
|
||||||
foreach ($query as $album) {
|
foreach ($query as $album) {
|
||||||
$albums[] = [
|
$albums[] = [
|
||||||
|
@ -23,24 +37,31 @@
|
||||||
'title' => $album->title,
|
'title' => $album->title,
|
||||||
'slug' => $album->slug,
|
'slug' => $album->slug,
|
||||||
'created_at' => $album->created_at,
|
'created_at' => $album->created_at,
|
||||||
'cover_url' => $album->getCoverUrl(Image::SMALL)
|
'covers' => [
|
||||||
|
'small' => $album->getCoverUrl(Image::SMALL),
|
||||||
|
'normal' => $album->getCoverUrl(Image::NORMAL)
|
||||||
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return Response::json($albums, 200);
|
return Response::json($albums, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function postCreate() {
|
|
||||||
return $this->execute(new CreateAlbumCommand(Input::all()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEdit($id) {
|
public function getEdit($id) {
|
||||||
$album = Album::find($id);
|
$album = Album::with('tracks')->find($id);
|
||||||
if (!$album)
|
if (!$album)
|
||||||
return $this->notFound('Album ' . $id . ' not found!');
|
return $this->notFound('Album ' . $id . ' not found!');
|
||||||
|
|
||||||
if ($album->user_id != Auth::user()->id)
|
if ($album->user_id != Auth::user()->id)
|
||||||
return $this->notAuthorized();
|
return $this->notAuthorized();
|
||||||
|
|
||||||
|
$tracks = [];
|
||||||
|
foreach ($album->tracks as $track) {
|
||||||
|
$tracks[] = [
|
||||||
|
'id' => $track->id,
|
||||||
|
'title' => $track->title
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return Response::json([
|
return Response::json([
|
||||||
'id' => $album->id,
|
'id' => $album->id,
|
||||||
'title' => $album->title,
|
'title' => $album->title,
|
||||||
|
@ -50,15 +71,8 @@
|
||||||
'published_at' => $album->published_at,
|
'published_at' => $album->published_at,
|
||||||
'description' => $album->description,
|
'description' => $album->description,
|
||||||
'cover_url' => $album->hasCover() ? $album->getCoverUrl(Image::NORMAL) : null,
|
'cover_url' => $album->hasCover() ? $album->getCoverUrl(Image::NORMAL) : null,
|
||||||
'real_cover_url' => $album->getCoverUrl(Image::NORMAL)
|
'real_cover_url' => $album->getCoverUrl(Image::NORMAL),
|
||||||
|
'tracks' => $tracks
|
||||||
], 200);
|
], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function postDelete($id) {
|
|
||||||
return $this->execute(new DeleteTrackCommand($id));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function putEdit($id) {
|
|
||||||
return $this->execute(new EditTrackCommand($id, Input::all()));
|
|
||||||
}
|
|
||||||
}
|
}
|
107
app/controllers/Api/Web/PlaylistsController.php
Normal file
107
app/controllers/Api/Web/PlaylistsController.php
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Api\Web;
|
||||||
|
|
||||||
|
use Commands\CreateAlbumCommand;
|
||||||
|
use Commands\CreatePlaylistCommand;
|
||||||
|
use Commands\DeleteAlbumCommand;
|
||||||
|
use Commands\DeletePlaylistCommand;
|
||||||
|
use Commands\DeleteTrackCommand;
|
||||||
|
use Commands\EditAlbumCommand;
|
||||||
|
use Commands\EditPlaylistCommand;
|
||||||
|
use Commands\EditTrackCommand;
|
||||||
|
use Cover;
|
||||||
|
use Entities\Album;
|
||||||
|
use Entities\Image;
|
||||||
|
use Entities\PinnedPlaylist;
|
||||||
|
use Entities\Playlist;
|
||||||
|
use Entities\Track;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Input;
|
||||||
|
use Illuminate\Support\Facades\Response;
|
||||||
|
|
||||||
|
class PlaylistsController extends \ApiControllerBase {
|
||||||
|
public function postCreate() {
|
||||||
|
return $this->execute(new CreatePlaylistCommand(Input::all()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postEdit($id) {
|
||||||
|
return $this->execute(new EditPlaylistCommand($id, Input::all()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postDelete($id) {
|
||||||
|
return $this->execute(new DeletePlaylistCommand($id, Input::all()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getShow($id) {
|
||||||
|
$playlist = Playlist::find($id);
|
||||||
|
if (!$playlist || !$playlist->canView(Auth::user()))
|
||||||
|
App::abort('404');
|
||||||
|
|
||||||
|
return Response::json([
|
||||||
|
'id' => $playlist->id,
|
||||||
|
'title' => $playlist->title,
|
||||||
|
'description' => $playlist->description,
|
||||||
|
'slug' => $playlist->slug,
|
||||||
|
'created_at' => $playlist->created_at,
|
||||||
|
'url' => $playlist->url,
|
||||||
|
'covers' => [
|
||||||
|
'small' => $playlist->getCoverUrl(Image::SMALL),
|
||||||
|
'normal' => $playlist->getCoverUrl(Image::NORMAL)
|
||||||
|
],
|
||||||
|
'is_pinned' => true,
|
||||||
|
'is_public' => $playlist->is_public == 1
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPinned() {
|
||||||
|
$query = Playlist::join('pinned_playlists', function($join) {
|
||||||
|
$join->on('playlist_id', '=', 'playlists.id');
|
||||||
|
})
|
||||||
|
->where('pinned_playlists.user_id', '=', Auth::user()->id)
|
||||||
|
->orderBy('title', 'asc')
|
||||||
|
->select('playlists.id', 'playlists.title', 'playlists.slug', 'playlists.created_at', 'playlists.user_id', 'playlists.is_public', 'playlists.description')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$playlists = [];
|
||||||
|
foreach ($query as $playlist) {
|
||||||
|
$playlists[] = [
|
||||||
|
'id' => $playlist->id,
|
||||||
|
'title' => $playlist->title,
|
||||||
|
'description' => $playlist->description,
|
||||||
|
'slug' => $playlist->slug,
|
||||||
|
'created_at' => $playlist->created_at,
|
||||||
|
'url' => $playlist->url,
|
||||||
|
'covers' => [
|
||||||
|
'small' => $playlist->getCoverUrl(Image::SMALL),
|
||||||
|
'normal' => $playlist->getCoverUrl(Image::NORMAL)
|
||||||
|
],
|
||||||
|
'is_pinned' => true,
|
||||||
|
'is_public' => $playlist->is_public == 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return Response::json($playlists, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOwned() {
|
||||||
|
$query = Playlist::summary()->with('pins')->where('user_id', \Auth::user()->id)->orderBy('title', 'asc')->get();
|
||||||
|
$playlists = [];
|
||||||
|
foreach ($query as $playlist) {
|
||||||
|
$playlists[] = [
|
||||||
|
'id' => $playlist->id,
|
||||||
|
'title' => $playlist->title,
|
||||||
|
'slug' => $playlist->slug,
|
||||||
|
'created_at' => $playlist->created_at,
|
||||||
|
'description' => $playlist->description,
|
||||||
|
'url' => $playlist->url,
|
||||||
|
'covers' => [
|
||||||
|
'small' => $playlist->getCoverUrl(Image::SMALL),
|
||||||
|
'normal' => $playlist->getCoverUrl(Image::NORMAL)
|
||||||
|
],
|
||||||
|
'is_pinned' => $playlist->hasPinFor(Auth::user()->id),
|
||||||
|
'is_public' => $playlist->is_public == 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return Response::json($playlists, 200);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,14 @@
|
||||||
return $this->execute(new UploadTrackCommand());
|
return $this->execute(new UploadTrackCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function postDelete($id) {
|
||||||
|
return $this->execute(new DeleteTrackCommand($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postEdit($id) {
|
||||||
|
return $this->execute(new EditTrackCommand($id, Input::all()));
|
||||||
|
}
|
||||||
|
|
||||||
public function getOwned() {
|
public function getOwned() {
|
||||||
$query = Track::summary()->whereNull('deleted_at')->where('user_id', \Auth::user()->id);
|
$query = Track::summary()->whereNull('deleted_at')->where('user_id', \Auth::user()->id);
|
||||||
|
|
||||||
|
@ -35,6 +43,13 @@
|
||||||
$query->orderBy($parts[0], $parts[1]);
|
$query->orderBy($parts[0], $parts[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Input::has('in_album')) {
|
||||||
|
if (Input::get('in_album') == 'true')
|
||||||
|
$query->whereNotNull('album_id');
|
||||||
|
else
|
||||||
|
$query->whereNull('album_id');
|
||||||
|
}
|
||||||
|
|
||||||
if (Input::has('genres'))
|
if (Input::has('genres'))
|
||||||
$query->whereIn('genre_id', Input::get('genres'));
|
$query->whereIn('genre_id', Input::get('genres'));
|
||||||
|
|
||||||
|
@ -99,15 +114,8 @@
|
||||||
'released_at' => $track->released_at,
|
'released_at' => $track->released_at,
|
||||||
'cover_url' => $track->hasCover() ? $track->getCoverUrl(Image::NORMAL) : null,
|
'cover_url' => $track->hasCover() ? $track->getCoverUrl(Image::NORMAL) : null,
|
||||||
'real_cover_url' => $track->getCoverUrl(Image::NORMAL),
|
'real_cover_url' => $track->getCoverUrl(Image::NORMAL),
|
||||||
'show_songs' => $showSongs
|
'show_songs' => $showSongs,
|
||||||
|
'album_id' => $track->album_id
|
||||||
], 200);
|
], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function postDelete($id) {
|
|
||||||
return $this->execute(new DeleteTrackCommand($id));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function putEdit($id) {
|
|
||||||
return $this->execute(new EditTrackCommand($id, Input::all()));
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Entities\Track;
|
||||||
|
|
||||||
class HomeController extends Controller {
|
class HomeController extends Controller {
|
||||||
public function getIndex() {
|
public function getIndex() {
|
||||||
return View::make('home.index');
|
return View::make('home.index');
|
||||||
|
|
|
@ -1,7 +1,29 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Entities\Playlist;
|
||||||
|
use Illuminate\Support\Facades\Redirect;
|
||||||
|
|
||||||
class PlaylistsController extends Controller {
|
class PlaylistsController extends Controller {
|
||||||
public function getIndex() {
|
public function getIndex() {
|
||||||
return View::make('playlists.index');
|
return View::make('playlists.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPlaylist($id, $slug) {
|
||||||
|
$playlist = Playlist::find($id);
|
||||||
|
if (!$playlist || !$playlist->canView(Auth::user()))
|
||||||
|
App::abort(404);
|
||||||
|
|
||||||
|
if ($playlist->slug != $slug)
|
||||||
|
return Redirect::action('PlaylistsController@getPlaylist', [$id, $playlist->slug]);
|
||||||
|
|
||||||
|
return View::make('playlists.show');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getShortlink($id) {
|
||||||
|
$playlist = Playlist::find($id);
|
||||||
|
if (!$playlist || !$playlist->canView(Auth::user()))
|
||||||
|
App::abort(404);
|
||||||
|
|
||||||
|
return Redirect::action('PlaylistsController@getPlaylist', [$id, $playlist->slug]);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreatePlaylists extends Migration {
|
||||||
|
public function up() {
|
||||||
|
Schema::create('playlists', function($table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->integer('user_id')->unsigned()->index();
|
||||||
|
$table->string('title');
|
||||||
|
$table->string('slug');
|
||||||
|
$table->text('description');
|
||||||
|
$table->boolean('is_public');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->date('deleted_at')->nullable()->index();
|
||||||
|
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->on_update('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('pinned_playlists', function($table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->integer('user_id')->unsigned()->index();
|
||||||
|
$table->integer('playlist_id')->unsigned()->index();
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->on_update('cascade');
|
||||||
|
$table->foreign('playlist_id')->references('id')->on('playlists')->on_update('cascade');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('playlist_track', function($table){
|
||||||
|
$table->increments('id');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->integer('playlist_id')->unsigned()->index();
|
||||||
|
$table->integer('track_id')->unsigned()->index();
|
||||||
|
$table->integer('position')->unsigned();
|
||||||
|
|
||||||
|
$table->foreign('playlist_id')->references('id')->on('playlists')->on_update('cascade')->on_delete('cascade');
|
||||||
|
$table->foreign('track_id')->references('id')->on('tracks')->on_update('cascade');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down() {
|
||||||
|
Schema::table('playlist_track', function($table){
|
||||||
|
$table->dropForeign('playlist_track_playlist_id_foreign');
|
||||||
|
$table->dropForeign('playlist_track_track_id_foreign');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::drop('playlist_track');
|
||||||
|
|
||||||
|
Schema::table('pinned_playlists', function($table){
|
||||||
|
$table->dropForeign('pinned_playlists_user_id_foreign');
|
||||||
|
$table->dropForeign('pinned_playlists_playlist_id_foreign');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::drop('pinned_playlists');
|
||||||
|
|
||||||
|
Schema::table('playlists', function($table){
|
||||||
|
$table->dropForeign('playlists_user_id_foreign');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::drop('playlists');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -49,10 +49,12 @@
|
||||||
new FileAsset('scripts/base/underscore.js'),
|
new FileAsset('scripts/base/underscore.js'),
|
||||||
new FileAsset('scripts/base/angular.js'),
|
new FileAsset('scripts/base/angular.js'),
|
||||||
new FileAsset('scripts/base/ui-bootstrap-tpls-0.4.0.js'),
|
new FileAsset('scripts/base/ui-bootstrap-tpls-0.4.0.js'),
|
||||||
|
new FileAsset('scripts/base/angular-ui-sortable.js'),
|
||||||
new FileAsset('scripts/base/angular-ui-date.js'),
|
new FileAsset('scripts/base/angular-ui-date.js'),
|
||||||
new FileAsset('scripts/base/angular-ui-router.js'),
|
new FileAsset('scripts/base/angular-ui-router.js'),
|
||||||
new AssetCollection([
|
new AssetCollection([
|
||||||
new GlobAsset('scripts/shared/*.coffee'),
|
new GlobAsset('scripts/shared/*.coffee'),
|
||||||
|
new GlobAsset('scripts/shared/*.js'),
|
||||||
new GlobAsset('scripts/app/*.coffee'),
|
new GlobAsset('scripts/app/*.coffee'),
|
||||||
new GlobAsset('scripts/app/services/*.coffee'),
|
new GlobAsset('scripts/app/services/*.coffee'),
|
||||||
new GlobAsset('scripts/app/filters/*.coffee'),
|
new GlobAsset('scripts/app/filters/*.coffee'),
|
||||||
|
|
|
@ -4,4 +4,8 @@
|
||||||
public static function template($template) {
|
public static function template($template) {
|
||||||
echo file_get_contents('templates/' . $template);
|
echo file_get_contents('templates/' . $template);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function angular($expression) {
|
||||||
|
return '{{' . $expression . '}}';
|
||||||
|
}
|
||||||
}
|
}
|
211
app/library/getid3/extension.cache.dbm.php
Normal file
211
app/library/getid3/extension.cache.dbm.php
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// extension.cache.dbm.php - part of getID3() //
|
||||||
|
// Please see readme.txt for more information //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// This extension written by Allan Hansen <ahØartemis*dk> //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a caching extension for getID3(). It works the exact same
|
||||||
|
* way as the getID3 class, but return cached information very fast
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* Normal getID3 usage (example):
|
||||||
|
*
|
||||||
|
* require_once 'getid3/getid3.php';
|
||||||
|
* $getID3 = new getID3;
|
||||||
|
* $getID3->encoding = 'UTF-8';
|
||||||
|
* $info1 = $getID3->analyze('file1.flac');
|
||||||
|
* $info2 = $getID3->analyze('file2.wv');
|
||||||
|
*
|
||||||
|
* getID3_cached usage:
|
||||||
|
*
|
||||||
|
* require_once 'getid3/getid3.php';
|
||||||
|
* require_once 'getid3/getid3/extension.cache.dbm.php';
|
||||||
|
* $getID3 = new getID3_cached('db3', '/tmp/getid3_cache.dbm',
|
||||||
|
* '/tmp/getid3_cache.lock');
|
||||||
|
* $getID3->encoding = 'UTF-8';
|
||||||
|
* $info1 = $getID3->analyze('file1.flac');
|
||||||
|
* $info2 = $getID3->analyze('file2.wv');
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Supported Cache Types
|
||||||
|
*
|
||||||
|
* SQL Databases: (use extension.cache.mysql)
|
||||||
|
*
|
||||||
|
* cache_type cache_options
|
||||||
|
* -------------------------------------------------------------------
|
||||||
|
* mysql host, database, username, password
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* DBM-Style Databases: (this extension)
|
||||||
|
*
|
||||||
|
* cache_type cache_options
|
||||||
|
* -------------------------------------------------------------------
|
||||||
|
* gdbm dbm_filename, lock_filename
|
||||||
|
* ndbm dbm_filename, lock_filename
|
||||||
|
* db2 dbm_filename, lock_filename
|
||||||
|
* db3 dbm_filename, lock_filename
|
||||||
|
* db4 dbm_filename, lock_filename (PHP5 required)
|
||||||
|
*
|
||||||
|
* PHP must have write access to both dbm_filename and lock_filename.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Recommended Cache Types
|
||||||
|
*
|
||||||
|
* Infrequent updates, many reads any DBM
|
||||||
|
* Frequent updates mysql
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class getID3_cached_dbm extends getID3
|
||||||
|
{
|
||||||
|
|
||||||
|
// public: constructor - see top of this file for cache type and cache_options
|
||||||
|
function getID3_cached_dbm($cache_type, $dbm_filename, $lock_filename) {
|
||||||
|
|
||||||
|
// Check for dba extension
|
||||||
|
if (!extension_loaded('dba')) {
|
||||||
|
throw new Exception('PHP is not compiled with dba support, required to use DBM style cache.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for specific dba driver
|
||||||
|
if (!function_exists('dba_handlers') || !in_array($cache_type, dba_handlers())) {
|
||||||
|
throw new Exception('PHP is not compiled --with '.$cache_type.' support, required to use DBM style cache.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create lock file if needed
|
||||||
|
if (!file_exists($lock_filename)) {
|
||||||
|
if (!touch($lock_filename)) {
|
||||||
|
throw new Exception('failed to create lock file: '.$lock_filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open lock file for writing
|
||||||
|
if (!is_writeable($lock_filename)) {
|
||||||
|
throw new Exception('lock file: '.$lock_filename.' is not writable');
|
||||||
|
}
|
||||||
|
$this->lock = fopen($lock_filename, 'w');
|
||||||
|
|
||||||
|
// Acquire exclusive write lock to lock file
|
||||||
|
flock($this->lock, LOCK_EX);
|
||||||
|
|
||||||
|
// Create dbm-file if needed
|
||||||
|
if (!file_exists($dbm_filename)) {
|
||||||
|
if (!touch($dbm_filename)) {
|
||||||
|
throw new Exception('failed to create dbm file: '.$dbm_filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to open dbm file for writing
|
||||||
|
$this->dba = dba_open($dbm_filename, 'w', $cache_type);
|
||||||
|
if (!$this->dba) {
|
||||||
|
|
||||||
|
// Failed - create new dbm file
|
||||||
|
$this->dba = dba_open($dbm_filename, 'n', $cache_type);
|
||||||
|
|
||||||
|
if (!$this->dba) {
|
||||||
|
throw new Exception('failed to create dbm file: '.$dbm_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert getID3 version number
|
||||||
|
dba_insert(getID3::VERSION, getID3::VERSION, $this->dba);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init misc values
|
||||||
|
$this->cache_type = $cache_type;
|
||||||
|
$this->dbm_filename = $dbm_filename;
|
||||||
|
|
||||||
|
// Register destructor
|
||||||
|
register_shutdown_function(array($this, '__destruct'));
|
||||||
|
|
||||||
|
// Check version number and clear cache if changed
|
||||||
|
if (dba_fetch(getID3::VERSION, $this->dba) != getID3::VERSION) {
|
||||||
|
$this->clear_cache();
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::getID3();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// public: destuctor
|
||||||
|
function __destruct() {
|
||||||
|
|
||||||
|
// Close dbm file
|
||||||
|
dba_close($this->dba);
|
||||||
|
|
||||||
|
// Release exclusive lock
|
||||||
|
flock($this->lock, LOCK_UN);
|
||||||
|
|
||||||
|
// Close lock file
|
||||||
|
fclose($this->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// public: clear cache
|
||||||
|
function clear_cache() {
|
||||||
|
|
||||||
|
// Close dbm file
|
||||||
|
dba_close($this->dba);
|
||||||
|
|
||||||
|
// Create new dbm file
|
||||||
|
$this->dba = dba_open($this->dbm_filename, 'n', $this->cache_type);
|
||||||
|
|
||||||
|
if (!$this->dba) {
|
||||||
|
throw new Exception('failed to clear cache/recreate dbm file: '.$this->dbm_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert getID3 version number
|
||||||
|
dba_insert(getID3::VERSION, getID3::VERSION, $this->dba);
|
||||||
|
|
||||||
|
// Re-register shutdown function
|
||||||
|
register_shutdown_function(array($this, '__destruct'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// public: analyze file
|
||||||
|
function analyze($filename) {
|
||||||
|
|
||||||
|
if (file_exists($filename)) {
|
||||||
|
|
||||||
|
// Calc key filename::mod_time::size - should be unique
|
||||||
|
$key = $filename.'::'.filemtime($filename).'::'.filesize($filename);
|
||||||
|
|
||||||
|
// Loopup key
|
||||||
|
$result = dba_fetch($key, $this->dba);
|
||||||
|
|
||||||
|
// Hit
|
||||||
|
if ($result !== false) {
|
||||||
|
return unserialize($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Miss
|
||||||
|
$result = parent::analyze($filename);
|
||||||
|
|
||||||
|
// Save result
|
||||||
|
if (file_exists($filename)) {
|
||||||
|
dba_insert($key, serialize($result), $this->dba);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
173
app/library/getid3/extension.cache.mysql.php
Normal file
173
app/library/getid3/extension.cache.mysql.php
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// extension.cache.mysql.php - part of getID3() //
|
||||||
|
// Please see readme.txt for more information //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// This extension written by Allan Hansen <ahØartemis*dk> //
|
||||||
|
// Table name mod by Carlo Capocasa <calroØcarlocapocasa*com> //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a caching extension for getID3(). It works the exact same
|
||||||
|
* way as the getID3 class, but return cached information very fast
|
||||||
|
*
|
||||||
|
* Example: (see also demo.cache.mysql.php in /demo/)
|
||||||
|
*
|
||||||
|
* Normal getID3 usage (example):
|
||||||
|
*
|
||||||
|
* require_once 'getid3/getid3.php';
|
||||||
|
* $getID3 = new getID3;
|
||||||
|
* $getID3->encoding = 'UTF-8';
|
||||||
|
* $info1 = $getID3->analyze('file1.flac');
|
||||||
|
* $info2 = $getID3->analyze('file2.wv');
|
||||||
|
*
|
||||||
|
* getID3_cached usage:
|
||||||
|
*
|
||||||
|
* require_once 'getid3/getid3.php';
|
||||||
|
* require_once 'getid3/getid3/extension.cache.mysql.php';
|
||||||
|
* // 5th parameter (tablename) is optional, default is 'getid3_cache'
|
||||||
|
* $getID3 = new getID3_cached_mysql('localhost', 'database', 'username', 'password', 'tablename');
|
||||||
|
* $getID3->encoding = 'UTF-8';
|
||||||
|
* $info1 = $getID3->analyze('file1.flac');
|
||||||
|
* $info2 = $getID3->analyze('file2.wv');
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Supported Cache Types (this extension)
|
||||||
|
*
|
||||||
|
* SQL Databases:
|
||||||
|
*
|
||||||
|
* cache_type cache_options
|
||||||
|
* -------------------------------------------------------------------
|
||||||
|
* mysql host, database, username, password
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* DBM-Style Databases: (use extension.cache.dbm)
|
||||||
|
*
|
||||||
|
* cache_type cache_options
|
||||||
|
* -------------------------------------------------------------------
|
||||||
|
* gdbm dbm_filename, lock_filename
|
||||||
|
* ndbm dbm_filename, lock_filename
|
||||||
|
* db2 dbm_filename, lock_filename
|
||||||
|
* db3 dbm_filename, lock_filename
|
||||||
|
* db4 dbm_filename, lock_filename (PHP5 required)
|
||||||
|
*
|
||||||
|
* PHP must have write access to both dbm_filename and lock_filename.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Recommended Cache Types
|
||||||
|
*
|
||||||
|
* Infrequent updates, many reads any DBM
|
||||||
|
* Frequent updates mysql
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class getID3_cached_mysql extends getID3
|
||||||
|
{
|
||||||
|
|
||||||
|
// private vars
|
||||||
|
var $cursor;
|
||||||
|
var $connection;
|
||||||
|
|
||||||
|
|
||||||
|
// public: constructor - see top of this file for cache type and cache_options
|
||||||
|
function getID3_cached_mysql($host, $database, $username, $password, $table='getid3_cache') {
|
||||||
|
|
||||||
|
// Check for mysql support
|
||||||
|
if (!function_exists('mysql_pconnect')) {
|
||||||
|
throw new Exception('PHP not compiled with mysql support.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to database
|
||||||
|
$this->connection = mysql_pconnect($host, $username, $password);
|
||||||
|
if (!$this->connection) {
|
||||||
|
throw new Exception('mysql_pconnect() failed - check permissions and spelling.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select database
|
||||||
|
if (!mysql_select_db($database, $this->connection)) {
|
||||||
|
throw new Exception('Cannot use database '.$database);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set table
|
||||||
|
$this->table = $table;
|
||||||
|
|
||||||
|
// Create cache table if not exists
|
||||||
|
$this->create_table();
|
||||||
|
|
||||||
|
// Check version number and clear cache if changed
|
||||||
|
$version = '';
|
||||||
|
if ($this->cursor = mysql_query("SELECT `value` FROM `".mysql_real_escape_string($this->table)."` WHERE (`filename` = '".mysql_real_escape_string(getID3::VERSION)."') AND (`filesize` = '-1') AND (`filetime` = '-1') AND (`analyzetime` = '-1')", $this->connection)) {
|
||||||
|
list($version) = mysql_fetch_array($this->cursor);
|
||||||
|
}
|
||||||
|
if ($version != getID3::VERSION) {
|
||||||
|
$this->clear_cache();
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::getID3();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// public: clear cache
|
||||||
|
function clear_cache() {
|
||||||
|
|
||||||
|
$this->cursor = mysql_query("DELETE FROM `".mysql_real_escape_string($this->table)."`", $this->connection);
|
||||||
|
$this->cursor = mysql_query("INSERT INTO `".mysql_real_escape_string($this->table)."` VALUES ('".getID3::VERSION."', -1, -1, -1, '".getID3::VERSION."')", $this->connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// public: analyze file
|
||||||
|
function analyze($filename) {
|
||||||
|
|
||||||
|
if (file_exists($filename)) {
|
||||||
|
|
||||||
|
// Short-hands
|
||||||
|
$filetime = filemtime($filename);
|
||||||
|
$filesize = filesize($filename);
|
||||||
|
|
||||||
|
// Lookup file
|
||||||
|
$this->cursor = mysql_query("SELECT `value` FROM `".mysql_real_escape_string($this->table)."` WHERE (`filename` = '".mysql_real_escape_string($filename)."') AND (`filesize` = '".mysql_real_escape_string($filesize)."') AND (`filetime` = '".mysql_real_escape_string($filetime)."')", $this->connection);
|
||||||
|
if (mysql_num_rows($this->cursor) > 0) {
|
||||||
|
// Hit
|
||||||
|
list($result) = mysql_fetch_array($this->cursor);
|
||||||
|
return unserialize(base64_decode($result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Miss
|
||||||
|
$analysis = parent::analyze($filename);
|
||||||
|
|
||||||
|
// Save result
|
||||||
|
if (file_exists($filename)) {
|
||||||
|
$this->cursor = mysql_query("INSERT INTO `".mysql_real_escape_string($this->table)."` (`filename`, `filesize`, `filetime`, `analyzetime`, `value`) VALUES ('".mysql_real_escape_string($filename)."', '".mysql_real_escape_string($filesize)."', '".mysql_real_escape_string($filetime)."', '".mysql_real_escape_string(time())."', '".mysql_real_escape_string(base64_encode(serialize($analysis)))."')", $this->connection);
|
||||||
|
}
|
||||||
|
return $analysis;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// private: (re)create sql table
|
||||||
|
function create_table($drop=false) {
|
||||||
|
|
||||||
|
$this->cursor = mysql_query("CREATE TABLE IF NOT EXISTS `".mysql_real_escape_string($this->table)."` (
|
||||||
|
`filename` VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
`filesize` INT(11) NOT NULL DEFAULT '0',
|
||||||
|
`filetime` INT(11) NOT NULL DEFAULT '0',
|
||||||
|
`analyzetime` INT(11) NOT NULL DEFAULT '0',
|
||||||
|
`value` TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (`filename`,`filesize`,`filetime`)) TYPE=MyISAM", $this->connection);
|
||||||
|
echo mysql_error($this->connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
1316
app/library/getid3/getid3.lib.php
Normal file
1316
app/library/getid3/getid3.lib.php
Normal file
File diff suppressed because it is too large
Load diff
1744
app/library/getid3/getid3.php
Normal file
1744
app/library/getid3/getid3.php
Normal file
File diff suppressed because it is too large
Load diff
280
app/library/getid3/module.archive.gzip.php
Normal file
280
app/library/getid3/module.archive.gzip.php
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.archive.gzip.php //
|
||||||
|
// module for analyzing GZIP files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// Module originally written by //
|
||||||
|
// Mike Mozolin <teddybearØmail*ru> //
|
||||||
|
// //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_gzip extends getid3_handler {
|
||||||
|
|
||||||
|
// public: Optional file list - disable for speed.
|
||||||
|
var $option_gzip_parse_contents = false; // decode gzipped files, if possible, and parse recursively (.tar.gz for example)
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'gzip';
|
||||||
|
|
||||||
|
$start_length = 10;
|
||||||
|
$unpack_header = 'a1id1/a1id2/a1cmethod/a1flags/a4mtime/a1xflags/a1os';
|
||||||
|
//+---+---+---+---+---+---+---+---+---+---+
|
||||||
|
//|ID1|ID2|CM |FLG| MTIME |XFL|OS |
|
||||||
|
//+---+---+---+---+---+---+---+---+---+---+
|
||||||
|
|
||||||
|
if ($info['filesize'] > $info['php_memory_limit']) {
|
||||||
|
$info['error'][] = 'File is too large ('.number_format($info['filesize']).' bytes) to read into memory (limit: '.number_format($info['php_memory_limit'] / 1048576).'MB)';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fseek($this->getid3->fp, 0);
|
||||||
|
$buffer = fread($this->getid3->fp, $info['filesize']);
|
||||||
|
|
||||||
|
$arr_members = explode("\x1F\x8B\x08", $buffer);
|
||||||
|
while (true) {
|
||||||
|
$is_wrong_members = false;
|
||||||
|
$num_members = intval(count($arr_members));
|
||||||
|
for ($i = 0; $i < $num_members; $i++) {
|
||||||
|
if (strlen($arr_members[$i]) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$buf = "\x1F\x8B\x08".$arr_members[$i];
|
||||||
|
|
||||||
|
$attr = unpack($unpack_header, substr($buf, 0, $start_length));
|
||||||
|
if (!$this->get_os_type(ord($attr['os']))) {
|
||||||
|
// Merge member with previous if wrong OS type
|
||||||
|
$arr_members[$i - 1] .= $buf;
|
||||||
|
$arr_members[$i] = '';
|
||||||
|
$is_wrong_members = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$is_wrong_members) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['gzip']['files'] = array();
|
||||||
|
|
||||||
|
$fpointer = 0;
|
||||||
|
$idx = 0;
|
||||||
|
for ($i = 0; $i < $num_members; $i++) {
|
||||||
|
if (strlen($arr_members[$i]) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$thisInfo = &$info['gzip']['member_header'][++$idx];
|
||||||
|
|
||||||
|
$buff = "\x1F\x8B\x08".$arr_members[$i];
|
||||||
|
|
||||||
|
$attr = unpack($unpack_header, substr($buff, 0, $start_length));
|
||||||
|
$thisInfo['filemtime'] = getid3_lib::LittleEndian2Int($attr['mtime']);
|
||||||
|
$thisInfo['raw']['id1'] = ord($attr['cmethod']);
|
||||||
|
$thisInfo['raw']['id2'] = ord($attr['cmethod']);
|
||||||
|
$thisInfo['raw']['cmethod'] = ord($attr['cmethod']);
|
||||||
|
$thisInfo['raw']['os'] = ord($attr['os']);
|
||||||
|
$thisInfo['raw']['xflags'] = ord($attr['xflags']);
|
||||||
|
$thisInfo['raw']['flags'] = ord($attr['flags']);
|
||||||
|
|
||||||
|
$thisInfo['flags']['crc16'] = (bool) ($thisInfo['raw']['flags'] & 0x02);
|
||||||
|
$thisInfo['flags']['extra'] = (bool) ($thisInfo['raw']['flags'] & 0x04);
|
||||||
|
$thisInfo['flags']['filename'] = (bool) ($thisInfo['raw']['flags'] & 0x08);
|
||||||
|
$thisInfo['flags']['comment'] = (bool) ($thisInfo['raw']['flags'] & 0x10);
|
||||||
|
|
||||||
|
$thisInfo['compression'] = $this->get_xflag_type($thisInfo['raw']['xflags']);
|
||||||
|
|
||||||
|
$thisInfo['os'] = $this->get_os_type($thisInfo['raw']['os']);
|
||||||
|
if (!$thisInfo['os']) {
|
||||||
|
$info['error'][] = 'Read error on gzip file';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fpointer = 10;
|
||||||
|
$arr_xsubfield = array();
|
||||||
|
// bit 2 - FLG.FEXTRA
|
||||||
|
//+---+---+=================================+
|
||||||
|
//| XLEN |...XLEN bytes of "extra field"...|
|
||||||
|
//+---+---+=================================+
|
||||||
|
if ($thisInfo['flags']['extra']) {
|
||||||
|
$w_xlen = substr($buff, $fpointer, 2);
|
||||||
|
$xlen = getid3_lib::LittleEndian2Int($w_xlen);
|
||||||
|
$fpointer += 2;
|
||||||
|
|
||||||
|
$thisInfo['raw']['xfield'] = substr($buff, $fpointer, $xlen);
|
||||||
|
// Extra SubFields
|
||||||
|
//+---+---+---+---+==================================+
|
||||||
|
//|SI1|SI2| LEN |... LEN bytes of subfield data ...|
|
||||||
|
//+---+---+---+---+==================================+
|
||||||
|
$idx = 0;
|
||||||
|
while (true) {
|
||||||
|
if ($idx >= $xlen) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$si1 = ord(substr($buff, $fpointer + $idx++, 1));
|
||||||
|
$si2 = ord(substr($buff, $fpointer + $idx++, 1));
|
||||||
|
if (($si1 == 0x41) && ($si2 == 0x70)) {
|
||||||
|
$w_xsublen = substr($buff, $fpointer + $idx, 2);
|
||||||
|
$xsublen = getid3_lib::LittleEndian2Int($w_xsublen);
|
||||||
|
$idx += 2;
|
||||||
|
$arr_xsubfield[] = substr($buff, $fpointer + $idx, $xsublen);
|
||||||
|
$idx += $xsublen;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$fpointer += $xlen;
|
||||||
|
}
|
||||||
|
// bit 3 - FLG.FNAME
|
||||||
|
//+=========================================+
|
||||||
|
//|...original file name, zero-terminated...|
|
||||||
|
//+=========================================+
|
||||||
|
// GZIP files may have only one file, with no filename, so assume original filename is current filename without .gz
|
||||||
|
$thisInfo['filename'] = preg_replace('#\.gz$#i', '', $info['filename']);
|
||||||
|
if ($thisInfo['flags']['filename']) {
|
||||||
|
while (true) {
|
||||||
|
if (ord($buff[$fpointer]) == 0) {
|
||||||
|
$fpointer++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$thisInfo['filename'] .= $buff[$fpointer];
|
||||||
|
$fpointer++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// bit 4 - FLG.FCOMMENT
|
||||||
|
//+===================================+
|
||||||
|
//|...file comment, zero-terminated...|
|
||||||
|
//+===================================+
|
||||||
|
if ($thisInfo['flags']['comment']) {
|
||||||
|
while (true) {
|
||||||
|
if (ord($buff[$fpointer]) == 0) {
|
||||||
|
$fpointer++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$thisInfo['comment'] .= $buff[$fpointer];
|
||||||
|
$fpointer++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// bit 1 - FLG.FHCRC
|
||||||
|
//+---+---+
|
||||||
|
//| CRC16 |
|
||||||
|
//+---+---+
|
||||||
|
if ($thisInfo['flags']['crc16']) {
|
||||||
|
$w_crc = substr($buff, $fpointer, 2);
|
||||||
|
$thisInfo['crc16'] = getid3_lib::LittleEndian2Int($w_crc);
|
||||||
|
$fpointer += 2;
|
||||||
|
}
|
||||||
|
// bit 0 - FLG.FTEXT
|
||||||
|
//if ($thisInfo['raw']['flags'] & 0x01) {
|
||||||
|
// Ignored...
|
||||||
|
//}
|
||||||
|
// bits 5, 6, 7 - reserved
|
||||||
|
|
||||||
|
$thisInfo['crc32'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 8, 4));
|
||||||
|
$thisInfo['filesize'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 4));
|
||||||
|
|
||||||
|
$info['gzip']['files'] = getid3_lib::array_merge_clobber($info['gzip']['files'], getid3_lib::CreateDeepArray($thisInfo['filename'], '/', $thisInfo['filesize']));
|
||||||
|
|
||||||
|
if ($this->option_gzip_parse_contents) {
|
||||||
|
// Try to inflate GZip
|
||||||
|
$csize = 0;
|
||||||
|
$inflated = '';
|
||||||
|
$chkcrc32 = '';
|
||||||
|
if (function_exists('gzinflate')) {
|
||||||
|
$cdata = substr($buff, $fpointer);
|
||||||
|
$cdata = substr($cdata, 0, strlen($cdata) - 8);
|
||||||
|
$csize = strlen($cdata);
|
||||||
|
$inflated = gzinflate($cdata);
|
||||||
|
|
||||||
|
// Calculate CRC32 for inflated content
|
||||||
|
$thisInfo['crc32_valid'] = (bool) (sprintf('%u', crc32($inflated)) == $thisInfo['crc32']);
|
||||||
|
|
||||||
|
// determine format
|
||||||
|
$formattest = substr($inflated, 0, 32774);
|
||||||
|
$getid3_temp = new getID3();
|
||||||
|
$determined_format = $getid3_temp->GetFileFormat($formattest);
|
||||||
|
unset($getid3_temp);
|
||||||
|
|
||||||
|
// file format is determined
|
||||||
|
$determined_format['module'] = (isset($determined_format['module']) ? $determined_format['module'] : '');
|
||||||
|
switch ($determined_format['module']) {
|
||||||
|
case 'tar':
|
||||||
|
// view TAR-file info
|
||||||
|
if (file_exists(GETID3_INCLUDEPATH.$determined_format['include']) && include_once(GETID3_INCLUDEPATH.$determined_format['include'])) {
|
||||||
|
if (($temp_tar_filename = tempnam(GETID3_TEMP_DIR, 'getID3')) === false) {
|
||||||
|
// can't find anywhere to create a temp file, abort
|
||||||
|
$info['error'][] = 'Unable to create temp file to parse TAR inside GZIP file';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($fp_temp_tar = fopen($temp_tar_filename, 'w+b')) {
|
||||||
|
fwrite($fp_temp_tar, $inflated);
|
||||||
|
fclose($fp_temp_tar);
|
||||||
|
$getid3_temp = new getID3();
|
||||||
|
$getid3_temp->openfile($temp_tar_filename);
|
||||||
|
$getid3_tar = new getid3_tar($getid3_temp);
|
||||||
|
$getid3_tar->Analyze();
|
||||||
|
$info['gzip']['member_header'][$idx]['tar'] = $getid3_temp->info['tar'];
|
||||||
|
unset($getid3_temp, $getid3_tar);
|
||||||
|
unlink($temp_tar_filename);
|
||||||
|
} else {
|
||||||
|
$info['error'][] = 'Unable to fopen() temp file to parse TAR inside GZIP file';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '':
|
||||||
|
default:
|
||||||
|
// unknown or unhandled format
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts the OS type
|
||||||
|
function get_os_type($key) {
|
||||||
|
static $os_type = array(
|
||||||
|
'0' => 'FAT filesystem (MS-DOS, OS/2, NT/Win32)',
|
||||||
|
'1' => 'Amiga',
|
||||||
|
'2' => 'VMS (or OpenVMS)',
|
||||||
|
'3' => 'Unix',
|
||||||
|
'4' => 'VM/CMS',
|
||||||
|
'5' => 'Atari TOS',
|
||||||
|
'6' => 'HPFS filesystem (OS/2, NT)',
|
||||||
|
'7' => 'Macintosh',
|
||||||
|
'8' => 'Z-System',
|
||||||
|
'9' => 'CP/M',
|
||||||
|
'10' => 'TOPS-20',
|
||||||
|
'11' => 'NTFS filesystem (NT)',
|
||||||
|
'12' => 'QDOS',
|
||||||
|
'13' => 'Acorn RISCOS',
|
||||||
|
'255' => 'unknown'
|
||||||
|
);
|
||||||
|
return (isset($os_type[$key]) ? $os_type[$key] : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts the eXtra FLags
|
||||||
|
function get_xflag_type($key) {
|
||||||
|
static $xflag_type = array(
|
||||||
|
'0' => 'unknown',
|
||||||
|
'2' => 'maximum compression',
|
||||||
|
'4' => 'fastest algorithm'
|
||||||
|
);
|
||||||
|
return (isset($xflag_type[$key]) ? $xflag_type[$key] : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
53
app/library/getid3/module.archive.rar.php
Normal file
53
app/library/getid3/module.archive.rar.php
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.archive.rar.php //
|
||||||
|
// module for analyzing RAR files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_rar extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
var $option_use_rar_extension = false;
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'rar';
|
||||||
|
|
||||||
|
if ($this->option_use_rar_extension === true) {
|
||||||
|
if (function_exists('rar_open')) {
|
||||||
|
if ($rp = rar_open($info['filenamepath'])) {
|
||||||
|
$info['rar']['files'] = array();
|
||||||
|
$entries = rar_list($rp);
|
||||||
|
foreach ($entries as $entry) {
|
||||||
|
$info['rar']['files'] = getid3_lib::array_merge_clobber($info['rar']['files'], getid3_lib::CreateDeepArray($entry->getName(), '/', $entry->getUnpackedSize()));
|
||||||
|
}
|
||||||
|
rar_close($rp);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$info['error'][] = 'failed to rar_open('.$info['filename'].')';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$info['error'][] = 'RAR support does not appear to be available in this PHP installation';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$info['error'][] = 'PHP-RAR processing has been disabled (set $getid3_rar->option_use_rar_extension=true to enable)';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
96
app/library/getid3/module.archive.szip.php
Normal file
96
app/library/getid3/module.archive.szip.php
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.archive.szip.php //
|
||||||
|
// module for analyzing SZIP compressed files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_szip extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$SZIPHeader = fread($this->getid3->fp, 6);
|
||||||
|
if (substr($SZIPHeader, 0, 4) != "SZ\x0A\x04") {
|
||||||
|
$info['error'][] = 'Expecting "53 5A 0A 04" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($SZIPHeader, 0, 4)).'"';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['fileformat'] = 'szip';
|
||||||
|
$info['szip']['major_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 4, 1));
|
||||||
|
$info['szip']['minor_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 5, 1));
|
||||||
|
|
||||||
|
while (!feof($this->getid3->fp)) {
|
||||||
|
$NextBlockID = fread($this->getid3->fp, 2);
|
||||||
|
switch ($NextBlockID) {
|
||||||
|
case 'SZ':
|
||||||
|
// Note that szip files can be concatenated, this has the same effect as
|
||||||
|
// concatenating the files. this also means that global header blocks
|
||||||
|
// might be present between directory/data blocks.
|
||||||
|
fseek($this->getid3->fp, 4, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'BH':
|
||||||
|
$BHheaderbytes = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 3));
|
||||||
|
$BHheaderdata = fread($this->getid3->fp, $BHheaderbytes);
|
||||||
|
$BHheaderoffset = 0;
|
||||||
|
while (strpos($BHheaderdata, "\x00", $BHheaderoffset) > 0) {
|
||||||
|
//filename as \0 terminated string (empty string indicates end)
|
||||||
|
//owner as \0 terminated string (empty is same as last file)
|
||||||
|
//group as \0 terminated string (empty is same as last file)
|
||||||
|
//3 byte filelength in this block
|
||||||
|
//2 byte access flags
|
||||||
|
//4 byte creation time (like in unix)
|
||||||
|
//4 byte modification time (like in unix)
|
||||||
|
//4 byte access time (like in unix)
|
||||||
|
|
||||||
|
$BHdataArray['filename'] = substr($BHheaderdata, $BHheaderoffset, strcspn($BHheaderdata, "\x00"));
|
||||||
|
$BHheaderoffset += (strlen($BHdataArray['filename']) + 1);
|
||||||
|
|
||||||
|
$BHdataArray['owner'] = substr($BHheaderdata, $BHheaderoffset, strcspn($BHheaderdata, "\x00"));
|
||||||
|
$BHheaderoffset += (strlen($BHdataArray['owner']) + 1);
|
||||||
|
|
||||||
|
$BHdataArray['group'] = substr($BHheaderdata, $BHheaderoffset, strcspn($BHheaderdata, "\x00"));
|
||||||
|
$BHheaderoffset += (strlen($BHdataArray['group']) + 1);
|
||||||
|
|
||||||
|
$BHdataArray['filelength'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 3));
|
||||||
|
$BHheaderoffset += 3;
|
||||||
|
|
||||||
|
$BHdataArray['access_flags'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 2));
|
||||||
|
$BHheaderoffset += 2;
|
||||||
|
|
||||||
|
$BHdataArray['creation_time'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4));
|
||||||
|
$BHheaderoffset += 4;
|
||||||
|
|
||||||
|
$BHdataArray['modification_time'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4));
|
||||||
|
$BHheaderoffset += 4;
|
||||||
|
|
||||||
|
$BHdataArray['access_time'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4));
|
||||||
|
$BHheaderoffset += 4;
|
||||||
|
|
||||||
|
$info['szip']['BH'][] = $BHdataArray;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
178
app/library/getid3/module.archive.tar.php
Normal file
178
app/library/getid3/module.archive.tar.php
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.archive.tar.php //
|
||||||
|
// module for analyzing TAR files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// Module originally written by //
|
||||||
|
// Mike Mozolin <teddybearØmail*ru> //
|
||||||
|
// //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_tar extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'tar';
|
||||||
|
$info['tar']['files'] = array();
|
||||||
|
|
||||||
|
$unpack_header = 'a100fname/a8mode/a8uid/a8gid/a12size/a12mtime/a8chksum/a1typflag/a100lnkname/a6magic/a2ver/a32uname/a32gname/a8devmaj/a8devmin/a155prefix';
|
||||||
|
$null_512k = str_repeat("\x00", 512); // end-of-file marker
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, 0);
|
||||||
|
while (!feof($this->getid3->fp)) {
|
||||||
|
$buffer = fread($this->getid3->fp, 512);
|
||||||
|
if (strlen($buffer) < 512) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the block
|
||||||
|
$checksum = 0;
|
||||||
|
for ($i = 0; $i < 148; $i++) {
|
||||||
|
$checksum += ord($buffer{$i});
|
||||||
|
}
|
||||||
|
for ($i = 148; $i < 156; $i++) {
|
||||||
|
$checksum += ord(' ');
|
||||||
|
}
|
||||||
|
for ($i = 156; $i < 512; $i++) {
|
||||||
|
$checksum += ord($buffer{$i});
|
||||||
|
}
|
||||||
|
$attr = unpack($unpack_header, $buffer);
|
||||||
|
$name = (isset($attr['fname'] ) ? trim($attr['fname'] ) : '');
|
||||||
|
$mode = octdec(isset($attr['mode'] ) ? trim($attr['mode'] ) : '');
|
||||||
|
$uid = octdec(isset($attr['uid'] ) ? trim($attr['uid'] ) : '');
|
||||||
|
$gid = octdec(isset($attr['gid'] ) ? trim($attr['gid'] ) : '');
|
||||||
|
$size = octdec(isset($attr['size'] ) ? trim($attr['size'] ) : '');
|
||||||
|
$mtime = octdec(isset($attr['mtime'] ) ? trim($attr['mtime'] ) : '');
|
||||||
|
$chksum = octdec(isset($attr['chksum'] ) ? trim($attr['chksum'] ) : '');
|
||||||
|
$typflag = (isset($attr['typflag']) ? trim($attr['typflag']) : '');
|
||||||
|
$lnkname = (isset($attr['lnkname']) ? trim($attr['lnkname']) : '');
|
||||||
|
$magic = (isset($attr['magic'] ) ? trim($attr['magic'] ) : '');
|
||||||
|
$ver = (isset($attr['ver'] ) ? trim($attr['ver'] ) : '');
|
||||||
|
$uname = (isset($attr['uname'] ) ? trim($attr['uname'] ) : '');
|
||||||
|
$gname = (isset($attr['gname'] ) ? trim($attr['gname'] ) : '');
|
||||||
|
$devmaj = octdec(isset($attr['devmaj'] ) ? trim($attr['devmaj'] ) : '');
|
||||||
|
$devmin = octdec(isset($attr['devmin'] ) ? trim($attr['devmin'] ) : '');
|
||||||
|
$prefix = (isset($attr['prefix'] ) ? trim($attr['prefix'] ) : '');
|
||||||
|
if (($checksum == 256) && ($chksum == 0)) {
|
||||||
|
// EOF Found
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($prefix) {
|
||||||
|
$name = $prefix.'/'.$name;
|
||||||
|
}
|
||||||
|
if ((preg_match('#/$#', $name)) && !$name) {
|
||||||
|
$typeflag = 5;
|
||||||
|
}
|
||||||
|
if ($buffer == $null_512k) {
|
||||||
|
// it's the end of the tar-file...
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read to the next chunk
|
||||||
|
fseek($this->getid3->fp, $size, SEEK_CUR);
|
||||||
|
|
||||||
|
$diff = $size % 512;
|
||||||
|
if ($diff != 0) {
|
||||||
|
// Padding, throw away
|
||||||
|
fseek($this->getid3->fp, (512 - $diff), SEEK_CUR);
|
||||||
|
}
|
||||||
|
// Protect against tar-files with garbage at the end
|
||||||
|
if ($name == '') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$info['tar']['file_details'][$name] = array (
|
||||||
|
'name' => $name,
|
||||||
|
'mode_raw' => $mode,
|
||||||
|
'mode' => getid3_tar::display_perms($mode),
|
||||||
|
'uid' => $uid,
|
||||||
|
'gid' => $gid,
|
||||||
|
'size' => $size,
|
||||||
|
'mtime' => $mtime,
|
||||||
|
'chksum' => $chksum,
|
||||||
|
'typeflag' => getid3_tar::get_flag_type($typflag),
|
||||||
|
'linkname' => $lnkname,
|
||||||
|
'magic' => $magic,
|
||||||
|
'version' => $ver,
|
||||||
|
'uname' => $uname,
|
||||||
|
'gname' => $gname,
|
||||||
|
'devmajor' => $devmaj,
|
||||||
|
'devminor' => $devmin
|
||||||
|
);
|
||||||
|
$info['tar']['files'] = getid3_lib::array_merge_clobber($info['tar']['files'], getid3_lib::CreateDeepArray($info['tar']['file_details'][$name]['name'], '/', $size));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses the file mode to file permissions
|
||||||
|
function display_perms($mode) {
|
||||||
|
// Determine Type
|
||||||
|
if ($mode & 0x1000) $type='p'; // FIFO pipe
|
||||||
|
elseif ($mode & 0x2000) $type='c'; // Character special
|
||||||
|
elseif ($mode & 0x4000) $type='d'; // Directory
|
||||||
|
elseif ($mode & 0x6000) $type='b'; // Block special
|
||||||
|
elseif ($mode & 0x8000) $type='-'; // Regular
|
||||||
|
elseif ($mode & 0xA000) $type='l'; // Symbolic Link
|
||||||
|
elseif ($mode & 0xC000) $type='s'; // Socket
|
||||||
|
else $type='u'; // UNKNOWN
|
||||||
|
|
||||||
|
// Determine permissions
|
||||||
|
$owner['read'] = (($mode & 00400) ? 'r' : '-');
|
||||||
|
$owner['write'] = (($mode & 00200) ? 'w' : '-');
|
||||||
|
$owner['execute'] = (($mode & 00100) ? 'x' : '-');
|
||||||
|
$group['read'] = (($mode & 00040) ? 'r' : '-');
|
||||||
|
$group['write'] = (($mode & 00020) ? 'w' : '-');
|
||||||
|
$group['execute'] = (($mode & 00010) ? 'x' : '-');
|
||||||
|
$world['read'] = (($mode & 00004) ? 'r' : '-');
|
||||||
|
$world['write'] = (($mode & 00002) ? 'w' : '-');
|
||||||
|
$world['execute'] = (($mode & 00001) ? 'x' : '-');
|
||||||
|
|
||||||
|
// Adjust for SUID, SGID and sticky bit
|
||||||
|
if ($mode & 0x800) $owner['execute'] = ($owner['execute'] == 'x') ? 's' : 'S';
|
||||||
|
if ($mode & 0x400) $group['execute'] = ($group['execute'] == 'x') ? 's' : 'S';
|
||||||
|
if ($mode & 0x200) $world['execute'] = ($world['execute'] == 'x') ? 't' : 'T';
|
||||||
|
|
||||||
|
$s = sprintf('%1s', $type);
|
||||||
|
$s .= sprintf('%1s%1s%1s', $owner['read'], $owner['write'], $owner['execute']);
|
||||||
|
$s .= sprintf('%1s%1s%1s', $group['read'], $group['write'], $group['execute']);
|
||||||
|
$s .= sprintf('%1s%1s%1s'."\n", $world['read'], $world['write'], $world['execute']);
|
||||||
|
return $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts the file type
|
||||||
|
function get_flag_type($typflag) {
|
||||||
|
static $flag_types = array(
|
||||||
|
'0' => 'LF_NORMAL',
|
||||||
|
'1' => 'LF_LINK',
|
||||||
|
'2' => 'LF_SYNLINK',
|
||||||
|
'3' => 'LF_CHR',
|
||||||
|
'4' => 'LF_BLK',
|
||||||
|
'5' => 'LF_DIR',
|
||||||
|
'6' => 'LF_FIFO',
|
||||||
|
'7' => 'LF_CONFIG',
|
||||||
|
'D' => 'LF_DUMPDIR',
|
||||||
|
'K' => 'LF_LONGLINK',
|
||||||
|
'L' => 'LF_LONGNAME',
|
||||||
|
'M' => 'LF_MULTIVOL',
|
||||||
|
'N' => 'LF_NAMES',
|
||||||
|
'S' => 'LF_SPARSE',
|
||||||
|
'V' => 'LF_VOLHDR'
|
||||||
|
);
|
||||||
|
return (isset($flag_types[$typflag]) ? $flag_types[$typflag] : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
424
app/library/getid3/module.archive.zip.php
Normal file
424
app/library/getid3/module.archive.zip.php
Normal file
|
@ -0,0 +1,424 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.archive.zip.php //
|
||||||
|
// module for analyzing pkZip files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_zip extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'zip';
|
||||||
|
$info['zip']['encoding'] = 'ISO-8859-1';
|
||||||
|
$info['zip']['files'] = array();
|
||||||
|
|
||||||
|
$info['zip']['compressed_size'] = 0;
|
||||||
|
$info['zip']['uncompressed_size'] = 0;
|
||||||
|
$info['zip']['entries_count'] = 0;
|
||||||
|
|
||||||
|
if (!getid3_lib::intValueSupported($info['filesize'])) {
|
||||||
|
$info['error'][] = 'File is larger than '.round(PHP_INT_MAX / 1073741824).'GB, not supported by PHP';
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
$EOCDsearchData = '';
|
||||||
|
$EOCDsearchCounter = 0;
|
||||||
|
while ($EOCDsearchCounter++ < 512) {
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, -128 * $EOCDsearchCounter, SEEK_END);
|
||||||
|
$EOCDsearchData = fread($this->getid3->fp, 128).$EOCDsearchData;
|
||||||
|
|
||||||
|
if (strstr($EOCDsearchData, 'PK'."\x05\x06")) {
|
||||||
|
|
||||||
|
$EOCDposition = strpos($EOCDsearchData, 'PK'."\x05\x06");
|
||||||
|
fseek($this->getid3->fp, (-128 * $EOCDsearchCounter) + $EOCDposition, SEEK_END);
|
||||||
|
$info['zip']['end_central_directory'] = $this->ZIPparseEndOfCentralDirectory();
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['zip']['end_central_directory']['directory_offset'], SEEK_SET);
|
||||||
|
$info['zip']['entries_count'] = 0;
|
||||||
|
while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($this->getid3->fp)) {
|
||||||
|
$info['zip']['central_directory'][] = $centraldirectoryentry;
|
||||||
|
$info['zip']['entries_count']++;
|
||||||
|
$info['zip']['compressed_size'] += $centraldirectoryentry['compressed_size'];
|
||||||
|
$info['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size'];
|
||||||
|
|
||||||
|
if ($centraldirectoryentry['uncompressed_size'] > 0) {
|
||||||
|
$info['zip']['files'] = getid3_lib::array_merge_clobber($info['zip']['files'], getid3_lib::CreateDeepArray($centraldirectoryentry['filename'], '/', $centraldirectoryentry['uncompressed_size']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($info['zip']['entries_count'] == 0) {
|
||||||
|
$info['error'][] = 'No Central Directory entries found (truncated file?)';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($info['zip']['end_central_directory']['comment'])) {
|
||||||
|
$info['zip']['comments']['comment'][] = $info['zip']['end_central_directory']['comment'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($info['zip']['central_directory'][0]['compression_method'])) {
|
||||||
|
$info['zip']['compression_method'] = $info['zip']['central_directory'][0]['compression_method'];
|
||||||
|
}
|
||||||
|
if (isset($info['zip']['central_directory'][0]['flags']['compression_speed'])) {
|
||||||
|
$info['zip']['compression_speed'] = $info['zip']['central_directory'][0]['flags']['compression_speed'];
|
||||||
|
}
|
||||||
|
if (isset($info['zip']['compression_method']) && ($info['zip']['compression_method'] == 'store') && !isset($info['zip']['compression_speed'])) {
|
||||||
|
$info['zip']['compression_speed'] = 'store';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->getZIPentriesFilepointer()) {
|
||||||
|
|
||||||
|
// central directory couldn't be found and/or parsed
|
||||||
|
// scan through actual file data entries, recover as much as possible from probable trucated file
|
||||||
|
if ($info['zip']['compressed_size'] > ($info['filesize'] - 46 - 22)) {
|
||||||
|
$info['error'][] = 'Warning: Truncated file! - Total compressed file sizes ('.$info['zip']['compressed_size'].' bytes) is greater than filesize minus Central Directory and End Of Central Directory structures ('.($info['filesize'] - 46 - 22).' bytes)';
|
||||||
|
}
|
||||||
|
$info['error'][] = 'Cannot find End Of Central Directory - returned list of files in [zip][entries] array may not be complete';
|
||||||
|
foreach ($info['zip']['entries'] as $key => $valuearray) {
|
||||||
|
$info['zip']['files'][$valuearray['filename']] = $valuearray['uncompressed_size'];
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
unset($info['zip']);
|
||||||
|
$info['fileformat'] = '';
|
||||||
|
$info['error'][] = 'Cannot find End Of Central Directory (truncated file?)';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getZIPHeaderFilepointerTopDown() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'zip';
|
||||||
|
|
||||||
|
$info['zip']['compressed_size'] = 0;
|
||||||
|
$info['zip']['uncompressed_size'] = 0;
|
||||||
|
$info['zip']['entries_count'] = 0;
|
||||||
|
|
||||||
|
rewind($this->getid3->fp);
|
||||||
|
while ($fileentry = $this->ZIPparseLocalFileHeader()) {
|
||||||
|
$info['zip']['entries'][] = $fileentry;
|
||||||
|
$info['zip']['entries_count']++;
|
||||||
|
}
|
||||||
|
if ($info['zip']['entries_count'] == 0) {
|
||||||
|
$info['error'][] = 'No Local File Header entries found';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['zip']['entries_count'] = 0;
|
||||||
|
while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($this->getid3->fp)) {
|
||||||
|
$info['zip']['central_directory'][] = $centraldirectoryentry;
|
||||||
|
$info['zip']['entries_count']++;
|
||||||
|
$info['zip']['compressed_size'] += $centraldirectoryentry['compressed_size'];
|
||||||
|
$info['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size'];
|
||||||
|
}
|
||||||
|
if ($info['zip']['entries_count'] == 0) {
|
||||||
|
$info['error'][] = 'No Central Directory entries found (truncated file?)';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($EOCD = $this->ZIPparseEndOfCentralDirectory()) {
|
||||||
|
$info['zip']['end_central_directory'] = $EOCD;
|
||||||
|
} else {
|
||||||
|
$info['error'][] = 'No End Of Central Directory entry found (truncated file?)';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($info['zip']['end_central_directory']['comment'])) {
|
||||||
|
$info['zip']['comments']['comment'][] = $info['zip']['end_central_directory']['comment'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getZIPentriesFilepointer() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['zip']['compressed_size'] = 0;
|
||||||
|
$info['zip']['uncompressed_size'] = 0;
|
||||||
|
$info['zip']['entries_count'] = 0;
|
||||||
|
|
||||||
|
rewind($this->getid3->fp);
|
||||||
|
while ($fileentry = $this->ZIPparseLocalFileHeader()) {
|
||||||
|
$info['zip']['entries'][] = $fileentry;
|
||||||
|
$info['zip']['entries_count']++;
|
||||||
|
$info['zip']['compressed_size'] += $fileentry['compressed_size'];
|
||||||
|
$info['zip']['uncompressed_size'] += $fileentry['uncompressed_size'];
|
||||||
|
}
|
||||||
|
if ($info['zip']['entries_count'] == 0) {
|
||||||
|
$info['error'][] = 'No Local File Header entries found';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ZIPparseLocalFileHeader() {
|
||||||
|
$LocalFileHeader['offset'] = ftell($this->getid3->fp);
|
||||||
|
|
||||||
|
$ZIPlocalFileHeader = fread($this->getid3->fp, 30);
|
||||||
|
|
||||||
|
$LocalFileHeader['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 0, 4));
|
||||||
|
if ($LocalFileHeader['raw']['signature'] != 0x04034B50) {
|
||||||
|
// invalid Local File Header Signature
|
||||||
|
fseek($this->getid3->fp, $LocalFileHeader['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$LocalFileHeader['raw']['extract_version'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 4, 2));
|
||||||
|
$LocalFileHeader['raw']['general_flags'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 6, 2));
|
||||||
|
$LocalFileHeader['raw']['compression_method'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 8, 2));
|
||||||
|
$LocalFileHeader['raw']['last_mod_file_time'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 10, 2));
|
||||||
|
$LocalFileHeader['raw']['last_mod_file_date'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 12, 2));
|
||||||
|
$LocalFileHeader['raw']['crc_32'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 14, 4));
|
||||||
|
$LocalFileHeader['raw']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 18, 4));
|
||||||
|
$LocalFileHeader['raw']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 22, 4));
|
||||||
|
$LocalFileHeader['raw']['filename_length'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 26, 2));
|
||||||
|
$LocalFileHeader['raw']['extra_field_length'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 28, 2));
|
||||||
|
|
||||||
|
$LocalFileHeader['extract_version'] = sprintf('%1.1f', $LocalFileHeader['raw']['extract_version'] / 10);
|
||||||
|
$LocalFileHeader['host_os'] = $this->ZIPversionOSLookup(($LocalFileHeader['raw']['extract_version'] & 0xFF00) >> 8);
|
||||||
|
$LocalFileHeader['compression_method'] = $this->ZIPcompressionMethodLookup($LocalFileHeader['raw']['compression_method']);
|
||||||
|
$LocalFileHeader['compressed_size'] = $LocalFileHeader['raw']['compressed_size'];
|
||||||
|
$LocalFileHeader['uncompressed_size'] = $LocalFileHeader['raw']['uncompressed_size'];
|
||||||
|
$LocalFileHeader['flags'] = $this->ZIPparseGeneralPurposeFlags($LocalFileHeader['raw']['general_flags'], $LocalFileHeader['raw']['compression_method']);
|
||||||
|
$LocalFileHeader['last_modified_timestamp'] = $this->DOStime2UNIXtime($LocalFileHeader['raw']['last_mod_file_date'], $LocalFileHeader['raw']['last_mod_file_time']);
|
||||||
|
|
||||||
|
$FilenameExtrafieldLength = $LocalFileHeader['raw']['filename_length'] + $LocalFileHeader['raw']['extra_field_length'];
|
||||||
|
if ($FilenameExtrafieldLength > 0) {
|
||||||
|
$ZIPlocalFileHeader .= fread($this->getid3->fp, $FilenameExtrafieldLength);
|
||||||
|
|
||||||
|
if ($LocalFileHeader['raw']['filename_length'] > 0) {
|
||||||
|
$LocalFileHeader['filename'] = substr($ZIPlocalFileHeader, 30, $LocalFileHeader['raw']['filename_length']);
|
||||||
|
}
|
||||||
|
if ($LocalFileHeader['raw']['extra_field_length'] > 0) {
|
||||||
|
$LocalFileHeader['raw']['extra_field_data'] = substr($ZIPlocalFileHeader, 30 + $LocalFileHeader['raw']['filename_length'], $LocalFileHeader['raw']['extra_field_length']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$LocalFileHeader['data_offset'] = ftell($this->getid3->fp);
|
||||||
|
//$LocalFileHeader['compressed_data'] = fread($this->getid3->fp, $LocalFileHeader['raw']['compressed_size']);
|
||||||
|
fseek($this->getid3->fp, $LocalFileHeader['raw']['compressed_size'], SEEK_CUR);
|
||||||
|
|
||||||
|
if ($LocalFileHeader['flags']['data_descriptor_used']) {
|
||||||
|
$DataDescriptor = fread($this->getid3->fp, 12);
|
||||||
|
$LocalFileHeader['data_descriptor']['crc_32'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 0, 4));
|
||||||
|
$LocalFileHeader['data_descriptor']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 4, 4));
|
||||||
|
$LocalFileHeader['data_descriptor']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 8, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $LocalFileHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ZIPparseCentralDirectory() {
|
||||||
|
$CentralDirectory['offset'] = ftell($this->getid3->fp);
|
||||||
|
|
||||||
|
$ZIPcentralDirectory = fread($this->getid3->fp, 46);
|
||||||
|
|
||||||
|
$CentralDirectory['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 0, 4));
|
||||||
|
if ($CentralDirectory['raw']['signature'] != 0x02014B50) {
|
||||||
|
// invalid Central Directory Signature
|
||||||
|
fseek($this->getid3->fp, $CentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$CentralDirectory['raw']['create_version'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 4, 2));
|
||||||
|
$CentralDirectory['raw']['extract_version'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 6, 2));
|
||||||
|
$CentralDirectory['raw']['general_flags'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 8, 2));
|
||||||
|
$CentralDirectory['raw']['compression_method'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 10, 2));
|
||||||
|
$CentralDirectory['raw']['last_mod_file_time'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 12, 2));
|
||||||
|
$CentralDirectory['raw']['last_mod_file_date'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 14, 2));
|
||||||
|
$CentralDirectory['raw']['crc_32'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 16, 4));
|
||||||
|
$CentralDirectory['raw']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 20, 4));
|
||||||
|
$CentralDirectory['raw']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 24, 4));
|
||||||
|
$CentralDirectory['raw']['filename_length'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 28, 2));
|
||||||
|
$CentralDirectory['raw']['extra_field_length'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 30, 2));
|
||||||
|
$CentralDirectory['raw']['file_comment_length'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 32, 2));
|
||||||
|
$CentralDirectory['raw']['disk_number_start'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 34, 2));
|
||||||
|
$CentralDirectory['raw']['internal_file_attrib'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 36, 2));
|
||||||
|
$CentralDirectory['raw']['external_file_attrib'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 38, 4));
|
||||||
|
$CentralDirectory['raw']['local_header_offset'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 42, 4));
|
||||||
|
|
||||||
|
$CentralDirectory['entry_offset'] = $CentralDirectory['raw']['local_header_offset'];
|
||||||
|
$CentralDirectory['create_version'] = sprintf('%1.1f', $CentralDirectory['raw']['create_version'] / 10);
|
||||||
|
$CentralDirectory['extract_version'] = sprintf('%1.1f', $CentralDirectory['raw']['extract_version'] / 10);
|
||||||
|
$CentralDirectory['host_os'] = $this->ZIPversionOSLookup(($CentralDirectory['raw']['extract_version'] & 0xFF00) >> 8);
|
||||||
|
$CentralDirectory['compression_method'] = $this->ZIPcompressionMethodLookup($CentralDirectory['raw']['compression_method']);
|
||||||
|
$CentralDirectory['compressed_size'] = $CentralDirectory['raw']['compressed_size'];
|
||||||
|
$CentralDirectory['uncompressed_size'] = $CentralDirectory['raw']['uncompressed_size'];
|
||||||
|
$CentralDirectory['flags'] = $this->ZIPparseGeneralPurposeFlags($CentralDirectory['raw']['general_flags'], $CentralDirectory['raw']['compression_method']);
|
||||||
|
$CentralDirectory['last_modified_timestamp'] = $this->DOStime2UNIXtime($CentralDirectory['raw']['last_mod_file_date'], $CentralDirectory['raw']['last_mod_file_time']);
|
||||||
|
|
||||||
|
$FilenameExtrafieldCommentLength = $CentralDirectory['raw']['filename_length'] + $CentralDirectory['raw']['extra_field_length'] + $CentralDirectory['raw']['file_comment_length'];
|
||||||
|
if ($FilenameExtrafieldCommentLength > 0) {
|
||||||
|
$FilenameExtrafieldComment = fread($this->getid3->fp, $FilenameExtrafieldCommentLength);
|
||||||
|
|
||||||
|
if ($CentralDirectory['raw']['filename_length'] > 0) {
|
||||||
|
$CentralDirectory['filename'] = substr($FilenameExtrafieldComment, 0, $CentralDirectory['raw']['filename_length']);
|
||||||
|
}
|
||||||
|
if ($CentralDirectory['raw']['extra_field_length'] > 0) {
|
||||||
|
$CentralDirectory['raw']['extra_field_data'] = substr($FilenameExtrafieldComment, $CentralDirectory['raw']['filename_length'], $CentralDirectory['raw']['extra_field_length']);
|
||||||
|
}
|
||||||
|
if ($CentralDirectory['raw']['file_comment_length'] > 0) {
|
||||||
|
$CentralDirectory['file_comment'] = substr($FilenameExtrafieldComment, $CentralDirectory['raw']['filename_length'] + $CentralDirectory['raw']['extra_field_length'], $CentralDirectory['raw']['file_comment_length']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $CentralDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ZIPparseEndOfCentralDirectory() {
|
||||||
|
$EndOfCentralDirectory['offset'] = ftell($this->getid3->fp);
|
||||||
|
|
||||||
|
$ZIPendOfCentralDirectory = fread($this->getid3->fp, 22);
|
||||||
|
|
||||||
|
$EndOfCentralDirectory['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 0, 4));
|
||||||
|
if ($EndOfCentralDirectory['signature'] != 0x06054B50) {
|
||||||
|
// invalid End Of Central Directory Signature
|
||||||
|
fseek($this->getid3->fp, $EndOfCentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$EndOfCentralDirectory['disk_number_current'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 4, 2));
|
||||||
|
$EndOfCentralDirectory['disk_number_start_directory'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 6, 2));
|
||||||
|
$EndOfCentralDirectory['directory_entries_this_disk'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 8, 2));
|
||||||
|
$EndOfCentralDirectory['directory_entries_total'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 10, 2));
|
||||||
|
$EndOfCentralDirectory['directory_size'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 12, 4));
|
||||||
|
$EndOfCentralDirectory['directory_offset'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 16, 4));
|
||||||
|
$EndOfCentralDirectory['comment_length'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 20, 2));
|
||||||
|
|
||||||
|
if ($EndOfCentralDirectory['comment_length'] > 0) {
|
||||||
|
$EndOfCentralDirectory['comment'] = fread($this->getid3->fp, $EndOfCentralDirectory['comment_length']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $EndOfCentralDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static function ZIPparseGeneralPurposeFlags($flagbytes, $compressionmethod) {
|
||||||
|
$ParsedFlags['encrypted'] = (bool) ($flagbytes & 0x0001);
|
||||||
|
|
||||||
|
switch ($compressionmethod) {
|
||||||
|
case 6:
|
||||||
|
$ParsedFlags['dictionary_size'] = (($flagbytes & 0x0002) ? 8192 : 4096);
|
||||||
|
$ParsedFlags['shannon_fano_trees'] = (($flagbytes & 0x0004) ? 3 : 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
case 9:
|
||||||
|
switch (($flagbytes & 0x0006) >> 1) {
|
||||||
|
case 0:
|
||||||
|
$ParsedFlags['compression_speed'] = 'normal';
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
$ParsedFlags['compression_speed'] = 'maximum';
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
$ParsedFlags['compression_speed'] = 'fast';
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
$ParsedFlags['compression_speed'] = 'superfast';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$ParsedFlags['data_descriptor_used'] = (bool) ($flagbytes & 0x0008);
|
||||||
|
|
||||||
|
return $ParsedFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static function ZIPversionOSLookup($index) {
|
||||||
|
static $ZIPversionOSLookup = array(
|
||||||
|
0 => 'MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)',
|
||||||
|
1 => 'Amiga',
|
||||||
|
2 => 'OpenVMS',
|
||||||
|
3 => 'Unix',
|
||||||
|
4 => 'VM/CMS',
|
||||||
|
5 => 'Atari ST',
|
||||||
|
6 => 'OS/2 H.P.F.S.',
|
||||||
|
7 => 'Macintosh',
|
||||||
|
8 => 'Z-System',
|
||||||
|
9 => 'CP/M',
|
||||||
|
10 => 'Windows NTFS',
|
||||||
|
11 => 'MVS',
|
||||||
|
12 => 'VSE',
|
||||||
|
13 => 'Acorn Risc',
|
||||||
|
14 => 'VFAT',
|
||||||
|
15 => 'Alternate MVS',
|
||||||
|
16 => 'BeOS',
|
||||||
|
17 => 'Tandem'
|
||||||
|
);
|
||||||
|
|
||||||
|
return (isset($ZIPversionOSLookup[$index]) ? $ZIPversionOSLookup[$index] : '[unknown]');
|
||||||
|
}
|
||||||
|
|
||||||
|
static function ZIPcompressionMethodLookup($index) {
|
||||||
|
static $ZIPcompressionMethodLookup = array(
|
||||||
|
0 => 'store',
|
||||||
|
1 => 'shrink',
|
||||||
|
2 => 'reduce-1',
|
||||||
|
3 => 'reduce-2',
|
||||||
|
4 => 'reduce-3',
|
||||||
|
5 => 'reduce-4',
|
||||||
|
6 => 'implode',
|
||||||
|
7 => 'tokenize',
|
||||||
|
8 => 'deflate',
|
||||||
|
9 => 'deflate64',
|
||||||
|
10 => 'PKWARE Date Compression Library Imploding'
|
||||||
|
);
|
||||||
|
|
||||||
|
return (isset($ZIPcompressionMethodLookup[$index]) ? $ZIPcompressionMethodLookup[$index] : '[unknown]');
|
||||||
|
}
|
||||||
|
|
||||||
|
static function DOStime2UNIXtime($DOSdate, $DOStime) {
|
||||||
|
// wFatDate
|
||||||
|
// Specifies the MS-DOS date. The date is a packed 16-bit value with the following format:
|
||||||
|
// Bits Contents
|
||||||
|
// 0-4 Day of the month (1-31)
|
||||||
|
// 5-8 Month (1 = January, 2 = February, and so on)
|
||||||
|
// 9-15 Year offset from 1980 (add 1980 to get actual year)
|
||||||
|
|
||||||
|
$UNIXday = ($DOSdate & 0x001F);
|
||||||
|
$UNIXmonth = (($DOSdate & 0x01E0) >> 5);
|
||||||
|
$UNIXyear = (($DOSdate & 0xFE00) >> 9) + 1980;
|
||||||
|
|
||||||
|
// wFatTime
|
||||||
|
// Specifies the MS-DOS time. The time is a packed 16-bit value with the following format:
|
||||||
|
// Bits Contents
|
||||||
|
// 0-4 Second divided by 2
|
||||||
|
// 5-10 Minute (0-59)
|
||||||
|
// 11-15 Hour (0-23 on a 24-hour clock)
|
||||||
|
|
||||||
|
$UNIXsecond = ($DOStime & 0x001F) * 2;
|
||||||
|
$UNIXminute = (($DOStime & 0x07E0) >> 5);
|
||||||
|
$UNIXhour = (($DOStime & 0xF800) >> 11);
|
||||||
|
|
||||||
|
return gmmktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
2021
app/library/getid3/module.audio-video.asf.php
Normal file
2021
app/library/getid3/module.audio-video.asf.php
Normal file
File diff suppressed because it is too large
Load diff
73
app/library/getid3/module.audio-video.bink.php
Normal file
73
app/library/getid3/module.audio-video.bink.php
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.bink.php //
|
||||||
|
// module for analyzing Bink or Smacker audio-video files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_bink extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['error'][] = 'Bink / Smacker files not properly processed by this version of getID3() ['.$this->getid3->version().']';
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$fileTypeID = fread($this->getid3->fp, 3);
|
||||||
|
switch ($fileTypeID) {
|
||||||
|
case 'BIK':
|
||||||
|
return $this->ParseBink();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'SMK':
|
||||||
|
return $this->ParseSmacker();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['error'][] = 'Expecting "BIK" or "SMK" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($fileTypeID).'"';
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function ParseBink() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
$info['fileformat'] = 'bink';
|
||||||
|
$info['video']['dataformat'] = 'bink';
|
||||||
|
|
||||||
|
$fileData = 'BIK'.fread($this->getid3->fp, 13);
|
||||||
|
|
||||||
|
$info['bink']['data_size'] = getid3_lib::LittleEndian2Int(substr($fileData, 4, 4));
|
||||||
|
$info['bink']['frame_count'] = getid3_lib::LittleEndian2Int(substr($fileData, 8, 2));
|
||||||
|
|
||||||
|
if (($info['avdataend'] - $info['avdataoffset']) != ($info['bink']['data_size'] + 8)) {
|
||||||
|
$info['error'][] = 'Probably truncated file: expecting '.$info['bink']['data_size'].' bytes, found '.($info['avdataend'] - $info['avdataoffset']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ParseSmacker() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
$info['fileformat'] = 'smacker';
|
||||||
|
$info['video']['dataformat'] = 'smacker';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
731
app/library/getid3/module.audio-video.flv.php
Normal file
731
app/library/getid3/module.audio-video.flv.php
Normal file
|
@ -0,0 +1,731 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
// //
|
||||||
|
// FLV module by Seth Kaufman <seth@whirl-i-gig.com> //
|
||||||
|
// //
|
||||||
|
// * version 0.1 (26 June 2005) //
|
||||||
|
// //
|
||||||
|
// //
|
||||||
|
// * version 0.1.1 (15 July 2005) //
|
||||||
|
// minor modifications by James Heinrich <info@getid3.org> //
|
||||||
|
// //
|
||||||
|
// * version 0.2 (22 February 2006) //
|
||||||
|
// Support for On2 VP6 codec and meta information //
|
||||||
|
// by Steve Webster <steve.webster@featurecreep.com> //
|
||||||
|
// //
|
||||||
|
// * version 0.3 (15 June 2006) //
|
||||||
|
// Modified to not read entire file into memory //
|
||||||
|
// by James Heinrich <info@getid3.org> //
|
||||||
|
// //
|
||||||
|
// * version 0.4 (07 December 2007) //
|
||||||
|
// Bugfixes for incorrectly parsed FLV dimensions //
|
||||||
|
// and incorrect parsing of onMetaTag //
|
||||||
|
// by Evgeny Moysevich <moysevich@gmail.com> //
|
||||||
|
// //
|
||||||
|
// * version 0.5 (21 May 2009) //
|
||||||
|
// Fixed parsing of audio tags and added additional codec //
|
||||||
|
// details. The duration is now read from onMetaTag (if //
|
||||||
|
// exists), rather than parsing whole file //
|
||||||
|
// by Nigel Barnes <ngbarnes@hotmail.com> //
|
||||||
|
// //
|
||||||
|
// * version 0.6 (24 May 2009) //
|
||||||
|
// Better parsing of files with h264 video //
|
||||||
|
// by Evgeny Moysevich <moysevichØgmail*com> //
|
||||||
|
// //
|
||||||
|
// * version 0.6.1 (30 May 2011) //
|
||||||
|
// prevent infinite loops in expGolombUe() //
|
||||||
|
// //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio-video.flv.php //
|
||||||
|
// module for analyzing Shockwave Flash Video files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
define('GETID3_FLV_TAG_AUDIO', 8);
|
||||||
|
define('GETID3_FLV_TAG_VIDEO', 9);
|
||||||
|
define('GETID3_FLV_TAG_META', 18);
|
||||||
|
|
||||||
|
define('GETID3_FLV_VIDEO_H263', 2);
|
||||||
|
define('GETID3_FLV_VIDEO_SCREEN', 3);
|
||||||
|
define('GETID3_FLV_VIDEO_VP6FLV', 4);
|
||||||
|
define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5);
|
||||||
|
define('GETID3_FLV_VIDEO_SCREENV2', 6);
|
||||||
|
define('GETID3_FLV_VIDEO_H264', 7);
|
||||||
|
|
||||||
|
define('H264_AVC_SEQUENCE_HEADER', 0);
|
||||||
|
define('H264_PROFILE_BASELINE', 66);
|
||||||
|
define('H264_PROFILE_MAIN', 77);
|
||||||
|
define('H264_PROFILE_EXTENDED', 88);
|
||||||
|
define('H264_PROFILE_HIGH', 100);
|
||||||
|
define('H264_PROFILE_HIGH10', 110);
|
||||||
|
define('H264_PROFILE_HIGH422', 122);
|
||||||
|
define('H264_PROFILE_HIGH444', 144);
|
||||||
|
define('H264_PROFILE_HIGH444_PREDICTIVE', 244);
|
||||||
|
|
||||||
|
class getid3_flv extends getid3_handler
|
||||||
|
{
|
||||||
|
var $max_frames = 100000; // break out of the loop if too many frames have been scanned; only scan this many if meta frame does not contain useful duration
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
|
||||||
|
$FLVdataLength = $info['avdataend'] - $info['avdataoffset'];
|
||||||
|
$FLVheader = fread($this->getid3->fp, 5);
|
||||||
|
|
||||||
|
$info['fileformat'] = 'flv';
|
||||||
|
$info['flv']['header']['signature'] = substr($FLVheader, 0, 3);
|
||||||
|
$info['flv']['header']['version'] = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1));
|
||||||
|
$TypeFlags = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));
|
||||||
|
|
||||||
|
$magic = 'FLV';
|
||||||
|
if ($info['flv']['header']['signature'] != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"';
|
||||||
|
unset($info['flv']);
|
||||||
|
unset($info['fileformat']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
|
||||||
|
$info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);
|
||||||
|
|
||||||
|
$FrameSizeDataLength = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 4));
|
||||||
|
$FLVheaderFrameLength = 9;
|
||||||
|
if ($FrameSizeDataLength > $FLVheaderFrameLength) {
|
||||||
|
fseek($this->getid3->fp, $FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
|
||||||
|
}
|
||||||
|
$Duration = 0;
|
||||||
|
$found_video = false;
|
||||||
|
$found_audio = false;
|
||||||
|
$found_meta = false;
|
||||||
|
$found_valid_meta_playtime = false;
|
||||||
|
$tagParseCount = 0;
|
||||||
|
$info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0);
|
||||||
|
$flv_framecount = &$info['flv']['framecount'];
|
||||||
|
while (((ftell($this->getid3->fp) + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime)) {
|
||||||
|
$ThisTagHeader = fread($this->getid3->fp, 16);
|
||||||
|
|
||||||
|
$PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 0, 4));
|
||||||
|
$TagType = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 4, 1));
|
||||||
|
$DataLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 5, 3));
|
||||||
|
$Timestamp = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 8, 3));
|
||||||
|
$LastHeaderByte = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
|
||||||
|
$NextOffset = ftell($this->getid3->fp) - 1 + $DataLength;
|
||||||
|
if ($Timestamp > $Duration) {
|
||||||
|
$Duration = $Timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
$flv_framecount['total']++;
|
||||||
|
switch ($TagType) {
|
||||||
|
case GETID3_FLV_TAG_AUDIO:
|
||||||
|
$flv_framecount['audio']++;
|
||||||
|
if (!$found_audio) {
|
||||||
|
$found_audio = true;
|
||||||
|
$info['flv']['audio']['audioFormat'] = ($LastHeaderByte >> 4) & 0x0F;
|
||||||
|
$info['flv']['audio']['audioRate'] = ($LastHeaderByte >> 2) & 0x03;
|
||||||
|
$info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01;
|
||||||
|
$info['flv']['audio']['audioType'] = $LastHeaderByte & 0x01;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GETID3_FLV_TAG_VIDEO:
|
||||||
|
$flv_framecount['video']++;
|
||||||
|
if (!$found_video) {
|
||||||
|
$found_video = true;
|
||||||
|
$info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
|
||||||
|
|
||||||
|
$FLVvideoHeader = fread($this->getid3->fp, 11);
|
||||||
|
|
||||||
|
if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {
|
||||||
|
// this code block contributed by: moysevichØgmail*com
|
||||||
|
|
||||||
|
$AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1));
|
||||||
|
if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) {
|
||||||
|
// read AVCDecoderConfigurationRecord
|
||||||
|
$configurationVersion = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 1));
|
||||||
|
$AVCProfileIndication = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 1));
|
||||||
|
$profile_compatibility = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 1));
|
||||||
|
$lengthSizeMinusOne = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 1));
|
||||||
|
$numOfSequenceParameterSets = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 8, 1));
|
||||||
|
|
||||||
|
if (($numOfSequenceParameterSets & 0x1F) != 0) {
|
||||||
|
// there is at least one SequenceParameterSet
|
||||||
|
// read size of the first SequenceParameterSet
|
||||||
|
//$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2));
|
||||||
|
$spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2));
|
||||||
|
// read the first SequenceParameterSet
|
||||||
|
$sps = fread($this->getid3->fp, $spsSize);
|
||||||
|
if (strlen($sps) == $spsSize) { // make sure that whole SequenceParameterSet was red
|
||||||
|
$spsReader = new AVCSequenceParameterSetReader($sps);
|
||||||
|
$spsReader->readData();
|
||||||
|
$info['video']['resolution_x'] = $spsReader->getWidth();
|
||||||
|
$info['video']['resolution_y'] = $spsReader->getHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// end: moysevichØgmail*com
|
||||||
|
|
||||||
|
} elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) {
|
||||||
|
|
||||||
|
$PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
|
||||||
|
$PictureSizeType = $PictureSizeType & 0x0007;
|
||||||
|
$info['flv']['header']['videoSizeType'] = $PictureSizeType;
|
||||||
|
switch ($PictureSizeType) {
|
||||||
|
case 0:
|
||||||
|
//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
|
||||||
|
//$PictureSizeEnc <<= 1;
|
||||||
|
//$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
|
||||||
|
//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
|
||||||
|
//$PictureSizeEnc <<= 1;
|
||||||
|
//$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;
|
||||||
|
|
||||||
|
$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2));
|
||||||
|
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
|
||||||
|
$PictureSizeEnc['x'] >>= 7;
|
||||||
|
$PictureSizeEnc['y'] >>= 7;
|
||||||
|
$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
|
||||||
|
$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3));
|
||||||
|
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3));
|
||||||
|
$PictureSizeEnc['x'] >>= 7;
|
||||||
|
$PictureSizeEnc['y'] >>= 7;
|
||||||
|
$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
|
||||||
|
$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
$info['video']['resolution_x'] = 352;
|
||||||
|
$info['video']['resolution_y'] = 288;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
$info['video']['resolution_x'] = 176;
|
||||||
|
$info['video']['resolution_y'] = 144;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
$info['video']['resolution_x'] = 128;
|
||||||
|
$info['video']['resolution_y'] = 96;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
$info['video']['resolution_x'] = 320;
|
||||||
|
$info['video']['resolution_y'] = 240;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
$info['video']['resolution_x'] = 160;
|
||||||
|
$info['video']['resolution_y'] = 120;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['video']['resolution_x'] = 0;
|
||||||
|
$info['video']['resolution_y'] = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Meta tag
|
||||||
|
case GETID3_FLV_TAG_META:
|
||||||
|
if (!$found_meta) {
|
||||||
|
$found_meta = true;
|
||||||
|
fseek($this->getid3->fp, -1, SEEK_CUR);
|
||||||
|
$datachunk = fread($this->getid3->fp, $DataLength);
|
||||||
|
$AMFstream = new AMFStream($datachunk);
|
||||||
|
$reader = new AMFReader($AMFstream);
|
||||||
|
$eventName = $reader->readData();
|
||||||
|
$info['flv']['meta'][$eventName] = $reader->readData();
|
||||||
|
unset($reader);
|
||||||
|
|
||||||
|
$copykeys = array('framerate'=>'frame_rate', 'width'=>'resolution_x', 'height'=>'resolution_y', 'audiodatarate'=>'bitrate', 'videodatarate'=>'bitrate');
|
||||||
|
foreach ($copykeys as $sourcekey => $destkey) {
|
||||||
|
if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) {
|
||||||
|
switch ($sourcekey) {
|
||||||
|
case 'width':
|
||||||
|
case 'height':
|
||||||
|
$info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey]));
|
||||||
|
break;
|
||||||
|
case 'audiodatarate':
|
||||||
|
$info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000));
|
||||||
|
break;
|
||||||
|
case 'videodatarate':
|
||||||
|
case 'frame_rate':
|
||||||
|
default:
|
||||||
|
$info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
|
||||||
|
$found_valid_meta_playtime = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// noop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fseek($this->getid3->fp, $NextOffset, SEEK_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = $Duration / 1000;
|
||||||
|
if ($info['playtime_seconds'] > 0) {
|
||||||
|
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($info['flv']['header']['hasAudio']) {
|
||||||
|
$info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['audio']['audioFormat']);
|
||||||
|
$info['audio']['sample_rate'] = $this->FLVaudioRate($info['flv']['audio']['audioRate']);
|
||||||
|
$info['audio']['bits_per_sample'] = $this->FLVaudioBitDepth($info['flv']['audio']['audioSampleSize']);
|
||||||
|
|
||||||
|
$info['audio']['channels'] = $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
|
||||||
|
$info['audio']['lossless'] = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
|
||||||
|
$info['audio']['dataformat'] = 'flv';
|
||||||
|
}
|
||||||
|
if (!empty($info['flv']['header']['hasVideo'])) {
|
||||||
|
$info['video']['codec'] = $this->FLVvideoCodec($info['flv']['video']['videoCodec']);
|
||||||
|
$info['video']['dataformat'] = 'flv';
|
||||||
|
$info['video']['lossless'] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set information from meta
|
||||||
|
if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
|
||||||
|
$info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration'];
|
||||||
|
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
}
|
||||||
|
if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) {
|
||||||
|
$info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['meta']['onMetaData']['audiocodecid']);
|
||||||
|
}
|
||||||
|
if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) {
|
||||||
|
$info['video']['codec'] = $this->FLVvideoCodec($info['flv']['meta']['onMetaData']['videocodecid']);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function FLVaudioFormat($id) {
|
||||||
|
$FLVaudioFormat = array(
|
||||||
|
0 => 'Linear PCM, platform endian',
|
||||||
|
1 => 'ADPCM',
|
||||||
|
2 => 'mp3',
|
||||||
|
3 => 'Linear PCM, little endian',
|
||||||
|
4 => 'Nellymoser 16kHz mono',
|
||||||
|
5 => 'Nellymoser 8kHz mono',
|
||||||
|
6 => 'Nellymoser',
|
||||||
|
7 => 'G.711A-law logarithmic PCM',
|
||||||
|
8 => 'G.711 mu-law logarithmic PCM',
|
||||||
|
9 => 'reserved',
|
||||||
|
10 => 'AAC',
|
||||||
|
11 => false, // unknown?
|
||||||
|
12 => false, // unknown?
|
||||||
|
13 => false, // unknown?
|
||||||
|
14 => 'mp3 8kHz',
|
||||||
|
15 => 'Device-specific sound',
|
||||||
|
);
|
||||||
|
return (isset($FLVaudioFormat[$id]) ? $FLVaudioFormat[$id] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FLVaudioRate($id) {
|
||||||
|
$FLVaudioRate = array(
|
||||||
|
0 => 5500,
|
||||||
|
1 => 11025,
|
||||||
|
2 => 22050,
|
||||||
|
3 => 44100,
|
||||||
|
);
|
||||||
|
return (isset($FLVaudioRate[$id]) ? $FLVaudioRate[$id] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FLVaudioBitDepth($id) {
|
||||||
|
$FLVaudioBitDepth = array(
|
||||||
|
0 => 8,
|
||||||
|
1 => 16,
|
||||||
|
);
|
||||||
|
return (isset($FLVaudioBitDepth[$id]) ? $FLVaudioBitDepth[$id] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FLVvideoCodec($id) {
|
||||||
|
$FLVvideoCodec = array(
|
||||||
|
GETID3_FLV_VIDEO_H263 => 'Sorenson H.263',
|
||||||
|
GETID3_FLV_VIDEO_SCREEN => 'Screen video',
|
||||||
|
GETID3_FLV_VIDEO_VP6FLV => 'On2 VP6',
|
||||||
|
GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel',
|
||||||
|
GETID3_FLV_VIDEO_SCREENV2 => 'Screen video v2',
|
||||||
|
GETID3_FLV_VIDEO_H264 => 'Sorenson H.264',
|
||||||
|
);
|
||||||
|
return (isset($FLVvideoCodec[$id]) ? $FLVvideoCodec[$id] : false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AMFStream {
|
||||||
|
var $bytes;
|
||||||
|
var $pos;
|
||||||
|
|
||||||
|
function AMFStream(&$bytes) {
|
||||||
|
$this->bytes =& $bytes;
|
||||||
|
$this->pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readByte() {
|
||||||
|
return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
function readInt() {
|
||||||
|
return ($this->readByte() << 8) + $this->readByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLong() {
|
||||||
|
return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
function readDouble() {
|
||||||
|
return getid3_lib::BigEndian2Float($this->read(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
function readUTF() {
|
||||||
|
$length = $this->readInt();
|
||||||
|
return $this->read($length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLongUTF() {
|
||||||
|
$length = $this->readLong();
|
||||||
|
return $this->read($length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function read($length) {
|
||||||
|
$val = substr($this->bytes, $this->pos, $length);
|
||||||
|
$this->pos += $length;
|
||||||
|
return $val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function peekByte() {
|
||||||
|
$pos = $this->pos;
|
||||||
|
$val = $this->readByte();
|
||||||
|
$this->pos = $pos;
|
||||||
|
return $val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function peekInt() {
|
||||||
|
$pos = $this->pos;
|
||||||
|
$val = $this->readInt();
|
||||||
|
$this->pos = $pos;
|
||||||
|
return $val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function peekLong() {
|
||||||
|
$pos = $this->pos;
|
||||||
|
$val = $this->readLong();
|
||||||
|
$this->pos = $pos;
|
||||||
|
return $val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function peekDouble() {
|
||||||
|
$pos = $this->pos;
|
||||||
|
$val = $this->readDouble();
|
||||||
|
$this->pos = $pos;
|
||||||
|
return $val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function peekUTF() {
|
||||||
|
$pos = $this->pos;
|
||||||
|
$val = $this->readUTF();
|
||||||
|
$this->pos = $pos;
|
||||||
|
return $val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function peekLongUTF() {
|
||||||
|
$pos = $this->pos;
|
||||||
|
$val = $this->readLongUTF();
|
||||||
|
$this->pos = $pos;
|
||||||
|
return $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AMFReader {
|
||||||
|
var $stream;
|
||||||
|
|
||||||
|
function AMFReader(&$stream) {
|
||||||
|
$this->stream =& $stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readData() {
|
||||||
|
$value = null;
|
||||||
|
|
||||||
|
$type = $this->stream->readByte();
|
||||||
|
switch ($type) {
|
||||||
|
|
||||||
|
// Double
|
||||||
|
case 0:
|
||||||
|
$value = $this->readDouble();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Boolean
|
||||||
|
case 1:
|
||||||
|
$value = $this->readBoolean();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// String
|
||||||
|
case 2:
|
||||||
|
$value = $this->readString();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Object
|
||||||
|
case 3:
|
||||||
|
$value = $this->readObject();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// null
|
||||||
|
case 6:
|
||||||
|
return null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Mixed array
|
||||||
|
case 8:
|
||||||
|
$value = $this->readMixedArray();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Array
|
||||||
|
case 10:
|
||||||
|
$value = $this->readArray();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Date
|
||||||
|
case 11:
|
||||||
|
$value = $this->readDate();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Long string
|
||||||
|
case 13:
|
||||||
|
$value = $this->readLongString();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// XML (handled as string)
|
||||||
|
case 15:
|
||||||
|
$value = $this->readXML();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Typed object (handled as object)
|
||||||
|
case 16:
|
||||||
|
$value = $this->readTypedObject();
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Long string
|
||||||
|
default:
|
||||||
|
$value = '(unknown or unsupported data type)';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readDouble() {
|
||||||
|
return $this->stream->readDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
function readBoolean() {
|
||||||
|
return $this->stream->readByte() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readString() {
|
||||||
|
return $this->stream->readUTF();
|
||||||
|
}
|
||||||
|
|
||||||
|
function readObject() {
|
||||||
|
// Get highest numerical index - ignored
|
||||||
|
// $highestIndex = $this->stream->readLong();
|
||||||
|
|
||||||
|
$data = array();
|
||||||
|
|
||||||
|
while ($key = $this->stream->readUTF()) {
|
||||||
|
$data[$key] = $this->readData();
|
||||||
|
}
|
||||||
|
// Mixed array record ends with empty string (0x00 0x00) and 0x09
|
||||||
|
if (($key == '') && ($this->stream->peekByte() == 0x09)) {
|
||||||
|
// Consume byte
|
||||||
|
$this->stream->readByte();
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readMixedArray() {
|
||||||
|
// Get highest numerical index - ignored
|
||||||
|
$highestIndex = $this->stream->readLong();
|
||||||
|
|
||||||
|
$data = array();
|
||||||
|
|
||||||
|
while ($key = $this->stream->readUTF()) {
|
||||||
|
if (is_numeric($key)) {
|
||||||
|
$key = (float) $key;
|
||||||
|
}
|
||||||
|
$data[$key] = $this->readData();
|
||||||
|
}
|
||||||
|
// Mixed array record ends with empty string (0x00 0x00) and 0x09
|
||||||
|
if (($key == '') && ($this->stream->peekByte() == 0x09)) {
|
||||||
|
// Consume byte
|
||||||
|
$this->stream->readByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readArray() {
|
||||||
|
$length = $this->stream->readLong();
|
||||||
|
$data = array();
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$data[] = $this->readData();
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readDate() {
|
||||||
|
$timestamp = $this->stream->readDouble();
|
||||||
|
$timezone = $this->stream->readInt();
|
||||||
|
return $timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLongString() {
|
||||||
|
return $this->stream->readLongUTF();
|
||||||
|
}
|
||||||
|
|
||||||
|
function readXML() {
|
||||||
|
return $this->stream->readLongUTF();
|
||||||
|
}
|
||||||
|
|
||||||
|
function readTypedObject() {
|
||||||
|
$className = $this->stream->readUTF();
|
||||||
|
return $this->readObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AVCSequenceParameterSetReader {
|
||||||
|
var $sps;
|
||||||
|
var $start = 0;
|
||||||
|
var $currentBytes = 0;
|
||||||
|
var $currentBits = 0;
|
||||||
|
var $width;
|
||||||
|
var $height;
|
||||||
|
|
||||||
|
function AVCSequenceParameterSetReader($sps) {
|
||||||
|
$this->sps = $sps;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readData() {
|
||||||
|
$this->skipBits(8);
|
||||||
|
$this->skipBits(8);
|
||||||
|
$profile = $this->getBits(8); // read profile
|
||||||
|
$this->skipBits(16);
|
||||||
|
$this->expGolombUe(); // read sps id
|
||||||
|
if (in_array($profile, array(H264_PROFILE_HIGH, H264_PROFILE_HIGH10, H264_PROFILE_HIGH422, H264_PROFILE_HIGH444, H264_PROFILE_HIGH444_PREDICTIVE))) {
|
||||||
|
if ($this->expGolombUe() == 3) {
|
||||||
|
$this->skipBits(1);
|
||||||
|
}
|
||||||
|
$this->expGolombUe();
|
||||||
|
$this->expGolombUe();
|
||||||
|
$this->skipBits(1);
|
||||||
|
if ($this->getBit()) {
|
||||||
|
for ($i = 0; $i < 8; $i++) {
|
||||||
|
if ($this->getBit()) {
|
||||||
|
$size = $i < 6 ? 16 : 64;
|
||||||
|
$lastScale = 8;
|
||||||
|
$nextScale = 8;
|
||||||
|
for ($j = 0; $j < $size; $j++) {
|
||||||
|
if ($nextScale != 0) {
|
||||||
|
$deltaScale = $this->expGolombUe();
|
||||||
|
$nextScale = ($lastScale + $deltaScale + 256) % 256;
|
||||||
|
}
|
||||||
|
if ($nextScale != 0) {
|
||||||
|
$lastScale = $nextScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->expGolombUe();
|
||||||
|
$pocType = $this->expGolombUe();
|
||||||
|
if ($pocType == 0) {
|
||||||
|
$this->expGolombUe();
|
||||||
|
} elseif ($pocType == 1) {
|
||||||
|
$this->skipBits(1);
|
||||||
|
$this->expGolombSe();
|
||||||
|
$this->expGolombSe();
|
||||||
|
$pocCycleLength = $this->expGolombUe();
|
||||||
|
for ($i = 0; $i < $pocCycleLength; $i++) {
|
||||||
|
$this->expGolombSe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->expGolombUe();
|
||||||
|
$this->skipBits(1);
|
||||||
|
$this->width = ($this->expGolombUe() + 1) * 16;
|
||||||
|
$heightMap = $this->expGolombUe() + 1;
|
||||||
|
$this->height = (2 - $this->getBit()) * $heightMap * 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipBits($bits) {
|
||||||
|
$newBits = $this->currentBits + $bits;
|
||||||
|
$this->currentBytes += (int)floor($newBits / 8);
|
||||||
|
$this->currentBits = $newBits % 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBit() {
|
||||||
|
$result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01;
|
||||||
|
$this->skipBits(1);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBits($bits) {
|
||||||
|
$result = 0;
|
||||||
|
for ($i = 0; $i < $bits; $i++) {
|
||||||
|
$result = ($result << 1) + $this->getBit();
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function expGolombUe() {
|
||||||
|
$significantBits = 0;
|
||||||
|
$bit = $this->getBit();
|
||||||
|
while ($bit == 0) {
|
||||||
|
$significantBits++;
|
||||||
|
$bit = $this->getBit();
|
||||||
|
|
||||||
|
if ($significantBits > 31) {
|
||||||
|
// something is broken, this is an emergency escape to prevent infinite loops
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (1 << $significantBits) + $this->getBits($significantBits) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function expGolombSe() {
|
||||||
|
$result = $this->expGolombUe();
|
||||||
|
if (($result & 0x01) == 0) {
|
||||||
|
return -($result >> 1);
|
||||||
|
} else {
|
||||||
|
return ($result + 1) >> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWidth() {
|
||||||
|
return $this->width;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHeight() {
|
||||||
|
return $this->height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
1706
app/library/getid3/module.audio-video.matroska.php
Normal file
1706
app/library/getid3/module.audio-video.matroska.php
Normal file
File diff suppressed because it is too large
Load diff
299
app/library/getid3/module.audio-video.mpeg.php
Normal file
299
app/library/getid3/module.audio-video.mpeg.php
Normal file
|
@ -0,0 +1,299 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio-video.mpeg.php //
|
||||||
|
// module for analyzing MPEG files //
|
||||||
|
// dependencies: module.audio.mp3.php //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true);
|
||||||
|
|
||||||
|
define('GETID3_MPEG_VIDEO_PICTURE_START', "\x00\x00\x01\x00");
|
||||||
|
define('GETID3_MPEG_VIDEO_USER_DATA_START', "\x00\x00\x01\xB2");
|
||||||
|
define('GETID3_MPEG_VIDEO_SEQUENCE_HEADER', "\x00\x00\x01\xB3");
|
||||||
|
define('GETID3_MPEG_VIDEO_SEQUENCE_ERROR', "\x00\x00\x01\xB4");
|
||||||
|
define('GETID3_MPEG_VIDEO_EXTENSION_START', "\x00\x00\x01\xB5");
|
||||||
|
define('GETID3_MPEG_VIDEO_SEQUENCE_END', "\x00\x00\x01\xB7");
|
||||||
|
define('GETID3_MPEG_VIDEO_GROUP_START', "\x00\x00\x01\xB8");
|
||||||
|
define('GETID3_MPEG_AUDIO_START', "\x00\x00\x01\xC0");
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_mpeg extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
if ($info['avdataend'] <= $info['avdataoffset']) {
|
||||||
|
$info['error'][] = '"avdataend" ('.$info['avdataend'].') is unexpectedly less-than-or-equal-to "avdataoffset" ('.$info['avdataoffset'].')';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['fileformat'] = 'mpeg';
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$MPEGstreamData = fread($this->getid3->fp, min(100000, $info['avdataend'] - $info['avdataoffset']));
|
||||||
|
$MPEGstreamDataLength = strlen($MPEGstreamData);
|
||||||
|
|
||||||
|
$foundVideo = true;
|
||||||
|
$VideoChunkOffset = 0;
|
||||||
|
while (substr($MPEGstreamData, $VideoChunkOffset++, 4) !== GETID3_MPEG_VIDEO_SEQUENCE_HEADER) {
|
||||||
|
if ($VideoChunkOffset >= $MPEGstreamDataLength) {
|
||||||
|
$foundVideo = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($foundVideo) {
|
||||||
|
|
||||||
|
// Start code 32 bits
|
||||||
|
// horizontal frame size 12 bits
|
||||||
|
// vertical frame size 12 bits
|
||||||
|
// pixel aspect ratio 4 bits
|
||||||
|
// frame rate 4 bits
|
||||||
|
// bitrate 18 bits
|
||||||
|
// marker bit 1 bit
|
||||||
|
// VBV buffer size 10 bits
|
||||||
|
// constrained parameter flag 1 bit
|
||||||
|
// intra quant. matrix flag 1 bit
|
||||||
|
// intra quant. matrix values 512 bits (present if matrix flag == 1)
|
||||||
|
// non-intra quant. matrix flag 1 bit
|
||||||
|
// non-intra quant. matrix values 512 bits (present if matrix flag == 1)
|
||||||
|
|
||||||
|
$info['video']['dataformat'] = 'mpeg';
|
||||||
|
|
||||||
|
$VideoChunkOffset += (strlen(GETID3_MPEG_VIDEO_SEQUENCE_HEADER) - 1);
|
||||||
|
|
||||||
|
$FrameSizeDWORD = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 3));
|
||||||
|
$VideoChunkOffset += 3;
|
||||||
|
|
||||||
|
$AspectRatioFrameRateDWORD = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 1));
|
||||||
|
$VideoChunkOffset += 1;
|
||||||
|
|
||||||
|
$assortedinformation = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 4));
|
||||||
|
$VideoChunkOffset += 4;
|
||||||
|
|
||||||
|
$info['mpeg']['video']['raw']['framesize_horizontal'] = ($FrameSizeDWORD & 0xFFF000) >> 12; // 12 bits for horizontal frame size
|
||||||
|
$info['mpeg']['video']['raw']['framesize_vertical'] = ($FrameSizeDWORD & 0x000FFF); // 12 bits for vertical frame size
|
||||||
|
$info['mpeg']['video']['raw']['pixel_aspect_ratio'] = ($AspectRatioFrameRateDWORD & 0xF0) >> 4;
|
||||||
|
$info['mpeg']['video']['raw']['frame_rate'] = ($AspectRatioFrameRateDWORD & 0x0F);
|
||||||
|
|
||||||
|
$info['mpeg']['video']['framesize_horizontal'] = $info['mpeg']['video']['raw']['framesize_horizontal'];
|
||||||
|
$info['mpeg']['video']['framesize_vertical'] = $info['mpeg']['video']['raw']['framesize_vertical'];
|
||||||
|
|
||||||
|
$info['mpeg']['video']['pixel_aspect_ratio'] = $this->MPEGvideoAspectRatioLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']);
|
||||||
|
$info['mpeg']['video']['pixel_aspect_ratio_text'] = $this->MPEGvideoAspectRatioTextLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']);
|
||||||
|
$info['mpeg']['video']['frame_rate'] = $this->MPEGvideoFramerateLookup($info['mpeg']['video']['raw']['frame_rate']);
|
||||||
|
|
||||||
|
$info['mpeg']['video']['raw']['bitrate'] = getid3_lib::Bin2Dec(substr($assortedinformation, 0, 18));
|
||||||
|
$info['mpeg']['video']['raw']['marker_bit'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 18, 1));
|
||||||
|
$info['mpeg']['video']['raw']['vbv_buffer_size'] = getid3_lib::Bin2Dec(substr($assortedinformation, 19, 10));
|
||||||
|
$info['mpeg']['video']['raw']['constrained_param_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 29, 1));
|
||||||
|
$info['mpeg']['video']['raw']['intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 30, 1));
|
||||||
|
if ($info['mpeg']['video']['raw']['intra_quant_flag']) {
|
||||||
|
|
||||||
|
// read 512 bits
|
||||||
|
$info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64));
|
||||||
|
$VideoChunkOffset += 64;
|
||||||
|
|
||||||
|
$info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($info['mpeg']['video']['raw']['intra_quant'], 511, 1));
|
||||||
|
$info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)).substr(getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)), 0, 511);
|
||||||
|
|
||||||
|
if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) {
|
||||||
|
$info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64);
|
||||||
|
$VideoChunkOffset += 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1));
|
||||||
|
if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) {
|
||||||
|
$info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64);
|
||||||
|
$VideoChunkOffset += 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($info['mpeg']['video']['raw']['bitrate'] == 0x3FFFF) { // 18 set bits
|
||||||
|
|
||||||
|
$info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] cannot determine average bitrate of VBR MPEG video files';
|
||||||
|
$info['mpeg']['video']['bitrate_mode'] = 'vbr';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['mpeg']['video']['bitrate'] = $info['mpeg']['video']['raw']['bitrate'] * 400;
|
||||||
|
$info['mpeg']['video']['bitrate_mode'] = 'cbr';
|
||||||
|
$info['video']['bitrate'] = $info['mpeg']['video']['bitrate'];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['video']['resolution_x'] = $info['mpeg']['video']['framesize_horizontal'];
|
||||||
|
$info['video']['resolution_y'] = $info['mpeg']['video']['framesize_vertical'];
|
||||||
|
$info['video']['frame_rate'] = $info['mpeg']['video']['frame_rate'];
|
||||||
|
$info['video']['bitrate_mode'] = $info['mpeg']['video']['bitrate_mode'];
|
||||||
|
$info['video']['pixel_aspect_ratio'] = $info['mpeg']['video']['pixel_aspect_ratio'];
|
||||||
|
$info['video']['lossless'] = false;
|
||||||
|
$info['video']['bits_per_sample'] = 24;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['error'][] = 'Could not find start of video block in the first 100,000 bytes (or before end of file) - this might not be an MPEG-video file?';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//0x000001B3 begins the sequence_header of every MPEG video stream.
|
||||||
|
//But in MPEG-2, this header must immediately be followed by an
|
||||||
|
//extension_start_code (0x000001B5) with a sequence_extension ID (1).
|
||||||
|
//(This extension contains all the additional MPEG-2 stuff.)
|
||||||
|
//MPEG-1 doesn't have this extension, so that's a sure way to tell the
|
||||||
|
//difference between MPEG-1 and MPEG-2 video streams.
|
||||||
|
|
||||||
|
if (substr($MPEGstreamData, $VideoChunkOffset, 4) == GETID3_MPEG_VIDEO_EXTENSION_START) {
|
||||||
|
$info['video']['codec'] = 'MPEG-2';
|
||||||
|
} else {
|
||||||
|
$info['video']['codec'] = 'MPEG-1';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$AudioChunkOffset = 0;
|
||||||
|
while (true) {
|
||||||
|
while (substr($MPEGstreamData, $AudioChunkOffset++, 4) !== GETID3_MPEG_AUDIO_START) {
|
||||||
|
if ($AudioChunkOffset >= $MPEGstreamDataLength) {
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$getid3_temp = new getID3();
|
||||||
|
$getid3_temp->openfile($this->getid3->filename);
|
||||||
|
$getid3_temp->info = $info;
|
||||||
|
$getid3_mp3 = new getid3_mp3($getid3_temp);
|
||||||
|
for ($i = 0; $i <= 7; $i++) {
|
||||||
|
// some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after
|
||||||
|
// I have no idea why or what the difference is, so this is a stupid hack.
|
||||||
|
// If anybody has any better idea of what's going on, please let me know - info@getid3.org
|
||||||
|
fseek($getid3_temp->fp, ftell($this->getid3->fp), SEEK_SET);
|
||||||
|
$getid3_temp->info = $info; // only overwrite real data if valid header found
|
||||||
|
if ($getid3_mp3->decodeMPEGaudioHeader(($AudioChunkOffset + 3) + 8 + $i, $getid3_temp->info, false)) {
|
||||||
|
$info = $getid3_temp->info;
|
||||||
|
$info['audio']['bitrate_mode'] = 'cbr';
|
||||||
|
$info['audio']['lossless'] = false;
|
||||||
|
unset($getid3_temp, $getid3_mp3);
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($getid3_temp, $getid3_mp3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary hack to account for interleaving overhead:
|
||||||
|
if (!empty($info['video']['bitrate']) && !empty($info['audio']['bitrate'])) {
|
||||||
|
$info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['video']['bitrate'] + $info['audio']['bitrate']);
|
||||||
|
|
||||||
|
// Interleaved MPEG audio/video files have a certain amount of overhead that varies
|
||||||
|
// by both video and audio bitrates, and not in any sensible, linear/logarithmic patter
|
||||||
|
// Use interpolated lookup tables to approximately guess how much is overhead, because
|
||||||
|
// playtime is calculated as filesize / total-bitrate
|
||||||
|
$info['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($info['video']['bitrate'], $info['audio']['bitrate']);
|
||||||
|
|
||||||
|
//switch ($info['video']['bitrate']) {
|
||||||
|
// case('5000000'):
|
||||||
|
// $multiplier = 0.93292642112380355828048824319889;
|
||||||
|
// break;
|
||||||
|
// case('5500000'):
|
||||||
|
// $multiplier = 0.93582895375200989965359777343219;
|
||||||
|
// break;
|
||||||
|
// case('6000000'):
|
||||||
|
// $multiplier = 0.93796247714820932532911373859139;
|
||||||
|
// break;
|
||||||
|
// case('7000000'):
|
||||||
|
// $multiplier = 0.9413264083635103463010117778776;
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// $multiplier = 1;
|
||||||
|
// break;
|
||||||
|
//}
|
||||||
|
//$info['playtime_seconds'] *= $multiplier;
|
||||||
|
//$info['warning'][] = 'Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.';
|
||||||
|
if ($info['video']['bitrate'] < 50000) {
|
||||||
|
$info['warning'][] = 'Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function MPEGsystemNonOverheadPercentage($VideoBitrate, $AudioBitrate) {
|
||||||
|
$OverheadPercentage = 0;
|
||||||
|
|
||||||
|
$AudioBitrate = max(min($AudioBitrate / 1000, 384), 32); // limit to range of 32kbps - 384kbps (should be only legal bitrates, but maybe VBR?)
|
||||||
|
$VideoBitrate = max(min($VideoBitrate / 1000, 10000), 10); // limit to range of 10kbps - 10Mbps (beyond that curves flatten anyways, no big loss)
|
||||||
|
|
||||||
|
|
||||||
|
//OMBB[audiobitrate] = array(video-10kbps, video-100kbps, video-1000kbps, video-10000kbps)
|
||||||
|
$OverheadMultiplierByBitrate[32] = array(0, 0.9676287944368530, 0.9802276264360310, 0.9844916183244460, 0.9852821845179940);
|
||||||
|
$OverheadMultiplierByBitrate[48] = array(0, 0.9779100089209830, 0.9787770035359320, 0.9846738664076130, 0.9852683013799960);
|
||||||
|
$OverheadMultiplierByBitrate[56] = array(0, 0.9731249855367600, 0.9776624308938040, 0.9832606361852130, 0.9843922606633340);
|
||||||
|
$OverheadMultiplierByBitrate[64] = array(0, 0.9755642683275760, 0.9795256705493390, 0.9836573009193170, 0.9851122539404470);
|
||||||
|
$OverheadMultiplierByBitrate[96] = array(0, 0.9788025247497290, 0.9798553314148700, 0.9822956869792560, 0.9834815119124690);
|
||||||
|
$OverheadMultiplierByBitrate[128] = array(0, 0.9816940050925480, 0.9821675936072120, 0.9829756927470870, 0.9839763420152050);
|
||||||
|
$OverheadMultiplierByBitrate[160] = array(0, 0.9825894094561180, 0.9820913399073960, 0.9823907143253970, 0.9832821783651570);
|
||||||
|
$OverheadMultiplierByBitrate[192] = array(0, 0.9832038474336260, 0.9825731694317960, 0.9821028622712400, 0.9828262076447620);
|
||||||
|
$OverheadMultiplierByBitrate[224] = array(0, 0.9836516298538770, 0.9824718601823890, 0.9818302180625380, 0.9823735101626480);
|
||||||
|
$OverheadMultiplierByBitrate[256] = array(0, 0.9845863022094920, 0.9837229411967540, 0.9824521662210830, 0.9828645172100790);
|
||||||
|
$OverheadMultiplierByBitrate[320] = array(0, 0.9849565280263180, 0.9837683142805110, 0.9822885275960400, 0.9824424382727190);
|
||||||
|
$OverheadMultiplierByBitrate[384] = array(0, 0.9856094774357600, 0.9844573394432720, 0.9825970399837330, 0.9824673808303890);
|
||||||
|
|
||||||
|
$BitrateToUseMin = 32;
|
||||||
|
$BitrateToUseMax = 32;
|
||||||
|
$previousBitrate = 32;
|
||||||
|
foreach ($OverheadMultiplierByBitrate as $key => $value) {
|
||||||
|
if ($AudioBitrate >= $previousBitrate) {
|
||||||
|
$BitrateToUseMin = $previousBitrate;
|
||||||
|
}
|
||||||
|
if ($AudioBitrate < $key) {
|
||||||
|
$BitrateToUseMax = $key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$previousBitrate = $key;
|
||||||
|
}
|
||||||
|
$FactorA = ($BitrateToUseMax - $AudioBitrate) / ($BitrateToUseMax - $BitrateToUseMin);
|
||||||
|
|
||||||
|
$VideoBitrateLog10 = log10($VideoBitrate);
|
||||||
|
$VideoFactorMin1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][floor($VideoBitrateLog10)];
|
||||||
|
$VideoFactorMin2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][floor($VideoBitrateLog10)];
|
||||||
|
$VideoFactorMax1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][ceil($VideoBitrateLog10)];
|
||||||
|
$VideoFactorMax2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][ceil($VideoBitrateLog10)];
|
||||||
|
$FactorV = $VideoBitrateLog10 - floor($VideoBitrateLog10);
|
||||||
|
|
||||||
|
$OverheadPercentage = $VideoFactorMin1 * $FactorA * $FactorV;
|
||||||
|
$OverheadPercentage += $VideoFactorMin2 * (1 - $FactorA) * $FactorV;
|
||||||
|
$OverheadPercentage += $VideoFactorMax1 * $FactorA * (1 - $FactorV);
|
||||||
|
$OverheadPercentage += $VideoFactorMax2 * (1 - $FactorA) * (1 - $FactorV);
|
||||||
|
|
||||||
|
return $OverheadPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function MPEGvideoFramerateLookup($rawframerate) {
|
||||||
|
$MPEGvideoFramerateLookup = array(0, 23.976, 24, 25, 29.97, 30, 50, 59.94, 60);
|
||||||
|
return (isset($MPEGvideoFramerateLookup[$rawframerate]) ? (float) $MPEGvideoFramerateLookup[$rawframerate] : (float) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MPEGvideoAspectRatioLookup($rawaspectratio) {
|
||||||
|
$MPEGvideoAspectRatioLookup = array(0, 1, 0.6735, 0.7031, 0.7615, 0.8055, 0.8437, 0.8935, 0.9157, 0.9815, 1.0255, 1.0695, 1.0950, 1.1575, 1.2015, 0);
|
||||||
|
return (isset($MPEGvideoAspectRatioLookup[$rawaspectratio]) ? (float) $MPEGvideoAspectRatioLookup[$rawaspectratio] : (float) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MPEGvideoAspectRatioTextLookup($rawaspectratio) {
|
||||||
|
$MPEGvideoAspectRatioTextLookup = array('forbidden', 'square pixels', '0.6735', '16:9, 625 line, PAL', '0.7615', '0.8055', '16:9, 525 line, NTSC', '0.8935', '4:3, 625 line, PAL, CCIR601', '0.9815', '1.0255', '1.0695', '4:3, 525 line, NTSC, CCIR601', '1.1575', '1.2015', 'reserved');
|
||||||
|
return (isset($MPEGvideoAspectRatioTextLookup[$rawaspectratio]) ? $MPEGvideoAspectRatioTextLookup[$rawaspectratio] : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
226
app/library/getid3/module.audio-video.nsv.php
Normal file
226
app/library/getid3/module.audio-video.nsv.php
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.nsv.php //
|
||||||
|
// module for analyzing Nullsoft NSV files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_nsv extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$NSVheader = fread($this->getid3->fp, 4);
|
||||||
|
|
||||||
|
switch ($NSVheader) {
|
||||||
|
case 'NSVs':
|
||||||
|
if ($this->getNSVsHeaderFilepointer(0)) {
|
||||||
|
$info['fileformat'] = 'nsv';
|
||||||
|
$info['audio']['dataformat'] = 'nsv';
|
||||||
|
$info['video']['dataformat'] = 'nsv';
|
||||||
|
$info['audio']['lossless'] = false;
|
||||||
|
$info['video']['lossless'] = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NSVf':
|
||||||
|
if ($this->getNSVfHeaderFilepointer(0)) {
|
||||||
|
$info['fileformat'] = 'nsv';
|
||||||
|
$info['audio']['dataformat'] = 'nsv';
|
||||||
|
$info['video']['dataformat'] = 'nsv';
|
||||||
|
$info['audio']['lossless'] = false;
|
||||||
|
$info['video']['lossless'] = false;
|
||||||
|
$this->getNSVsHeaderFilepointer($info['nsv']['NSVf']['header_length']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['error'][] = 'Expecting "NSVs" or "NSVf" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($NSVheader).'"';
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($info['nsv']['NSVf'])) {
|
||||||
|
$info['warning'][] = 'NSVf header not present - cannot calculate playtime or bitrate';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNSVsHeaderFilepointer($fileoffset) {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
fseek($this->getid3->fp, $fileoffset, SEEK_SET);
|
||||||
|
$NSVsheader = fread($this->getid3->fp, 28);
|
||||||
|
$offset = 0;
|
||||||
|
|
||||||
|
$info['nsv']['NSVs']['identifier'] = substr($NSVsheader, $offset, 4);
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
if ($info['nsv']['NSVs']['identifier'] != 'NSVs') {
|
||||||
|
$info['error'][] = 'expected "NSVs" at offset ('.$fileoffset.'), found "'.$info['nsv']['NSVs']['identifier'].'" instead';
|
||||||
|
unset($info['nsv']['NSVs']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['nsv']['NSVs']['offset'] = $fileoffset;
|
||||||
|
|
||||||
|
$info['nsv']['NSVs']['video_codec'] = substr($NSVsheader, $offset, 4);
|
||||||
|
$offset += 4;
|
||||||
|
$info['nsv']['NSVs']['audio_codec'] = substr($NSVsheader, $offset, 4);
|
||||||
|
$offset += 4;
|
||||||
|
$info['nsv']['NSVs']['resolution_x'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$info['nsv']['NSVs']['resolution_y'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
|
||||||
|
$info['nsv']['NSVs']['framerate_index'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
//$info['nsv']['NSVs']['unknown1b'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
//$info['nsv']['NSVs']['unknown1c'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
//$info['nsv']['NSVs']['unknown1d'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
//$info['nsv']['NSVs']['unknown2a'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
//$info['nsv']['NSVs']['unknown2b'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
//$info['nsv']['NSVs']['unknown2c'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
//$info['nsv']['NSVs']['unknown2d'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
|
||||||
|
switch ($info['nsv']['NSVs']['audio_codec']) {
|
||||||
|
case 'PCM ':
|
||||||
|
$info['nsv']['NSVs']['bits_channel'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
$info['nsv']['NSVs']['channels'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
$info['nsv']['NSVs']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
|
||||||
|
$info['audio']['sample_rate'] = $info['nsv']['NSVs']['sample_rate'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'MP3 ':
|
||||||
|
case 'NONE':
|
||||||
|
default:
|
||||||
|
//$info['nsv']['NSVs']['unknown3'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['video']['resolution_x'] = $info['nsv']['NSVs']['resolution_x'];
|
||||||
|
$info['video']['resolution_y'] = $info['nsv']['NSVs']['resolution_y'];
|
||||||
|
$info['nsv']['NSVs']['frame_rate'] = $this->NSVframerateLookup($info['nsv']['NSVs']['framerate_index']);
|
||||||
|
$info['video']['frame_rate'] = $info['nsv']['NSVs']['frame_rate'];
|
||||||
|
$info['video']['bits_per_sample'] = 24;
|
||||||
|
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNSVfHeaderFilepointer($fileoffset, $getTOCoffsets=false) {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
fseek($this->getid3->fp, $fileoffset, SEEK_SET);
|
||||||
|
$NSVfheader = fread($this->getid3->fp, 28);
|
||||||
|
$offset = 0;
|
||||||
|
|
||||||
|
$info['nsv']['NSVf']['identifier'] = substr($NSVfheader, $offset, 4);
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
if ($info['nsv']['NSVf']['identifier'] != 'NSVf') {
|
||||||
|
$info['error'][] = 'expected "NSVf" at offset ('.$fileoffset.'), found "'.$info['nsv']['NSVf']['identifier'].'" instead';
|
||||||
|
unset($info['nsv']['NSVf']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['nsv']['NSVs']['offset'] = $fileoffset;
|
||||||
|
|
||||||
|
$info['nsv']['NSVf']['header_length'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$info['nsv']['NSVf']['file_size'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
if ($info['nsv']['NSVf']['file_size'] > $info['avdataend']) {
|
||||||
|
$info['warning'][] = 'truncated file - NSVf header indicates '.$info['nsv']['NSVf']['file_size'].' bytes, file actually '.$info['avdataend'].' bytes';
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['nsv']['NSVf']['playtime_ms'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$info['nsv']['NSVf']['meta_size'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$info['nsv']['NSVf']['TOC_entries_1'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$info['nsv']['NSVf']['TOC_entries_2'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
if ($info['nsv']['NSVf']['playtime_ms'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt NSV file: NSVf.playtime_ms == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$NSVfheader .= fread($this->getid3->fp, $info['nsv']['NSVf']['meta_size'] + (4 * $info['nsv']['NSVf']['TOC_entries_1']) + (4 * $info['nsv']['NSVf']['TOC_entries_2']));
|
||||||
|
$NSVfheaderlength = strlen($NSVfheader);
|
||||||
|
$info['nsv']['NSVf']['metadata'] = substr($NSVfheader, $offset, $info['nsv']['NSVf']['meta_size']);
|
||||||
|
$offset += $info['nsv']['NSVf']['meta_size'];
|
||||||
|
|
||||||
|
if ($getTOCoffsets) {
|
||||||
|
$TOCcounter = 0;
|
||||||
|
while ($TOCcounter < $info['nsv']['NSVf']['TOC_entries_1']) {
|
||||||
|
if ($TOCcounter < $info['nsv']['NSVf']['TOC_entries_1']) {
|
||||||
|
$info['nsv']['NSVf']['TOC_1'][$TOCcounter] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$TOCcounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trim($info['nsv']['NSVf']['metadata']) != '') {
|
||||||
|
$info['nsv']['NSVf']['metadata'] = str_replace('`', "\x01", $info['nsv']['NSVf']['metadata']);
|
||||||
|
$CommentPairArray = explode("\x01".' ', $info['nsv']['NSVf']['metadata']);
|
||||||
|
foreach ($CommentPairArray as $CommentPair) {
|
||||||
|
if (strstr($CommentPair, '='."\x01")) {
|
||||||
|
list($key, $value) = explode('='."\x01", $CommentPair, 2);
|
||||||
|
$info['nsv']['comments'][strtolower($key)][] = trim(str_replace("\x01", '', $value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = $info['nsv']['NSVf']['playtime_ms'] / 1000;
|
||||||
|
$info['bitrate'] = ($info['nsv']['NSVf']['file_size'] * 8) / $info['playtime_seconds'];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static function NSVframerateLookup($framerateindex) {
|
||||||
|
if ($framerateindex <= 127) {
|
||||||
|
return (float) $framerateindex;
|
||||||
|
}
|
||||||
|
static $NSVframerateLookup = array();
|
||||||
|
if (empty($NSVframerateLookup)) {
|
||||||
|
$NSVframerateLookup[129] = (float) 29.970;
|
||||||
|
$NSVframerateLookup[131] = (float) 23.976;
|
||||||
|
$NSVframerateLookup[133] = (float) 14.985;
|
||||||
|
$NSVframerateLookup[197] = (float) 59.940;
|
||||||
|
$NSVframerateLookup[199] = (float) 47.952;
|
||||||
|
}
|
||||||
|
return (isset($NSVframerateLookup[$framerateindex]) ? $NSVframerateLookup[$framerateindex] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
2134
app/library/getid3/module.audio-video.quicktime.php
Normal file
2134
app/library/getid3/module.audio-video.quicktime.php
Normal file
File diff suppressed because it is too large
Load diff
530
app/library/getid3/module.audio-video.real.php
Normal file
530
app/library/getid3/module.audio-video.real.php
Normal file
|
@ -0,0 +1,530 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio-video.real.php //
|
||||||
|
// module for analyzing Real Audio/Video files //
|
||||||
|
// dependencies: module.audio-video.riff.php //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||||
|
|
||||||
|
class getid3_real extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'real';
|
||||||
|
$info['bitrate'] = 0;
|
||||||
|
$info['playtime_seconds'] = 0;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$ChunkCounter = 0;
|
||||||
|
while (ftell($this->getid3->fp) < $info['avdataend']) {
|
||||||
|
$ChunkData = fread($this->getid3->fp, 8);
|
||||||
|
$ChunkName = substr($ChunkData, 0, 4);
|
||||||
|
$ChunkSize = getid3_lib::BigEndian2Int(substr($ChunkData, 4, 4));
|
||||||
|
|
||||||
|
if ($ChunkName == '.ra'."\xFD") {
|
||||||
|
$ChunkData .= fread($this->getid3->fp, $ChunkSize - 8);
|
||||||
|
if ($this->ParseOldRAheader(substr($ChunkData, 0, 128), $info['real']['old_ra_header'])) {
|
||||||
|
$info['audio']['dataformat'] = 'real';
|
||||||
|
$info['audio']['lossless'] = false;
|
||||||
|
$info['audio']['sample_rate'] = $info['real']['old_ra_header']['sample_rate'];
|
||||||
|
$info['audio']['bits_per_sample'] = $info['real']['old_ra_header']['bits_per_sample'];
|
||||||
|
$info['audio']['channels'] = $info['real']['old_ra_header']['channels'];
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = 60 * ($info['real']['old_ra_header']['audio_bytes'] / $info['real']['old_ra_header']['bytes_per_minute']);
|
||||||
|
$info['audio']['bitrate'] = 8 * ($info['real']['old_ra_header']['audio_bytes'] / $info['playtime_seconds']);
|
||||||
|
$info['audio']['codec'] = $this->RealAudioCodecFourCClookup($info['real']['old_ra_header']['fourcc'], $info['audio']['bitrate']);
|
||||||
|
|
||||||
|
foreach ($info['real']['old_ra_header']['comments'] as $key => $valuearray) {
|
||||||
|
if (strlen(trim($valuearray[0])) > 0) {
|
||||||
|
$info['real']['comments'][$key][] = trim($valuearray[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$info['error'][] = 'There was a problem parsing this RealAudio file. Please submit it for analysis to info@getid3.org';
|
||||||
|
unset($info['bitrate']);
|
||||||
|
unset($info['playtime_seconds']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$info['real']['chunks'][$ChunkCounter] = array();
|
||||||
|
$thisfile_real_chunks_currentchunk = &$info['real']['chunks'][$ChunkCounter];
|
||||||
|
|
||||||
|
$thisfile_real_chunks_currentchunk['name'] = $ChunkName;
|
||||||
|
$thisfile_real_chunks_currentchunk['offset'] = ftell($this->getid3->fp) - 8;
|
||||||
|
$thisfile_real_chunks_currentchunk['length'] = $ChunkSize;
|
||||||
|
if (($thisfile_real_chunks_currentchunk['offset'] + $thisfile_real_chunks_currentchunk['length']) > $info['avdataend']) {
|
||||||
|
$info['warning'][] = 'Chunk "'.$thisfile_real_chunks_currentchunk['name'].'" at offset '.$thisfile_real_chunks_currentchunk['offset'].' claims to be '.$thisfile_real_chunks_currentchunk['length'].' bytes long, which is beyond end of file';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ChunkSize > ($this->getid3->fread_buffer_size() + 8)) {
|
||||||
|
|
||||||
|
$ChunkData .= fread($this->getid3->fp, $this->getid3->fread_buffer_size() - 8);
|
||||||
|
fseek($this->getid3->fp, $thisfile_real_chunks_currentchunk['offset'] + $ChunkSize, SEEK_SET);
|
||||||
|
|
||||||
|
} elseif(($ChunkSize - 8) > 0) {
|
||||||
|
|
||||||
|
$ChunkData .= fread($this->getid3->fp, $ChunkSize - 8);
|
||||||
|
|
||||||
|
}
|
||||||
|
$offset = 8;
|
||||||
|
|
||||||
|
switch ($ChunkName) {
|
||||||
|
|
||||||
|
case '.RMF': // RealMedia File Header
|
||||||
|
$thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
switch ($thisfile_real_chunks_currentchunk['object_version']) {
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
$thisfile_real_chunks_currentchunk['file_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['headers_count'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
//$info['warning'][] = 'Expected .RMF-object_version to be "0", actual value is "'.$thisfile_real_chunks_currentchunk['object_version'].'" (should not be a problem)';
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'PROP': // Properties Header
|
||||||
|
$thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
if ($thisfile_real_chunks_currentchunk['object_version'] == 0) {
|
||||||
|
$thisfile_real_chunks_currentchunk['max_bit_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['avg_bit_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['max_packet_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['avg_packet_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['num_packets'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['duration'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['preroll'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['index_offset'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['data_offset'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['num_streams'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_real_chunks_currentchunk['flags_raw'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$info['playtime_seconds'] = $thisfile_real_chunks_currentchunk['duration'] / 1000;
|
||||||
|
if ($thisfile_real_chunks_currentchunk['duration'] > 0) {
|
||||||
|
$info['bitrate'] += $thisfile_real_chunks_currentchunk['avg_bit_rate'];
|
||||||
|
}
|
||||||
|
$thisfile_real_chunks_currentchunk['flags']['save_enabled'] = (bool) ($thisfile_real_chunks_currentchunk['flags_raw'] & 0x0001);
|
||||||
|
$thisfile_real_chunks_currentchunk['flags']['perfect_play'] = (bool) ($thisfile_real_chunks_currentchunk['flags_raw'] & 0x0002);
|
||||||
|
$thisfile_real_chunks_currentchunk['flags']['live_broadcast'] = (bool) ($thisfile_real_chunks_currentchunk['flags_raw'] & 0x0004);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'MDPR': // Media Properties Header
|
||||||
|
$thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
if ($thisfile_real_chunks_currentchunk['object_version'] == 0) {
|
||||||
|
$thisfile_real_chunks_currentchunk['stream_number'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_real_chunks_currentchunk['max_bit_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['avg_bit_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['max_packet_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['avg_packet_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['start_time'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['preroll'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['duration'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['stream_name_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
$thisfile_real_chunks_currentchunk['stream_name'] = substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['stream_name_size']);
|
||||||
|
$offset += $thisfile_real_chunks_currentchunk['stream_name_size'];
|
||||||
|
$thisfile_real_chunks_currentchunk['mime_type_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
$thisfile_real_chunks_currentchunk['mime_type'] = substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['mime_type_size']);
|
||||||
|
$offset += $thisfile_real_chunks_currentchunk['mime_type_size'];
|
||||||
|
$thisfile_real_chunks_currentchunk['type_specific_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['type_specific_data'] = substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['type_specific_len']);
|
||||||
|
$offset += $thisfile_real_chunks_currentchunk['type_specific_len'];
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$thisfile_real_chunks_currentchunk_typespecificdata = &$thisfile_real_chunks_currentchunk['type_specific_data'];
|
||||||
|
|
||||||
|
switch ($thisfile_real_chunks_currentchunk['mime_type']) {
|
||||||
|
case 'video/x-pn-realvideo':
|
||||||
|
case 'video/x-pn-multirate-realvideo':
|
||||||
|
// http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$thisfile_real_chunks_currentchunk['video_info'] = array();
|
||||||
|
$thisfile_real_chunks_currentchunk_videoinfo = &$thisfile_real_chunks_currentchunk['video_info'];
|
||||||
|
|
||||||
|
$thisfile_real_chunks_currentchunk_videoinfo['dwSize'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 0, 4));
|
||||||
|
$thisfile_real_chunks_currentchunk_videoinfo['fourcc1'] = substr($thisfile_real_chunks_currentchunk_typespecificdata, 4, 4);
|
||||||
|
$thisfile_real_chunks_currentchunk_videoinfo['fourcc2'] = substr($thisfile_real_chunks_currentchunk_typespecificdata, 8, 4);
|
||||||
|
$thisfile_real_chunks_currentchunk_videoinfo['width'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 12, 2));
|
||||||
|
$thisfile_real_chunks_currentchunk_videoinfo['height'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 14, 2));
|
||||||
|
$thisfile_real_chunks_currentchunk_videoinfo['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 16, 2));
|
||||||
|
//$thisfile_real_chunks_currentchunk_videoinfo['unknown1'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 18, 2));
|
||||||
|
//$thisfile_real_chunks_currentchunk_videoinfo['unknown2'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 20, 2));
|
||||||
|
$thisfile_real_chunks_currentchunk_videoinfo['frames_per_second'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 22, 2));
|
||||||
|
//$thisfile_real_chunks_currentchunk_videoinfo['unknown3'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 24, 2));
|
||||||
|
//$thisfile_real_chunks_currentchunk_videoinfo['unknown4'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 26, 2));
|
||||||
|
//$thisfile_real_chunks_currentchunk_videoinfo['unknown5'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 28, 2));
|
||||||
|
//$thisfile_real_chunks_currentchunk_videoinfo['unknown6'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 30, 2));
|
||||||
|
//$thisfile_real_chunks_currentchunk_videoinfo['unknown7'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 32, 2));
|
||||||
|
//$thisfile_real_chunks_currentchunk_videoinfo['unknown8'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 34, 2));
|
||||||
|
//$thisfile_real_chunks_currentchunk_videoinfo['unknown9'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 36, 2));
|
||||||
|
|
||||||
|
$thisfile_real_chunks_currentchunk_videoinfo['codec'] = getid3_riff::RIFFfourccLookup($thisfile_real_chunks_currentchunk_videoinfo['fourcc2']);
|
||||||
|
|
||||||
|
$info['video']['resolution_x'] = $thisfile_real_chunks_currentchunk_videoinfo['width'];
|
||||||
|
$info['video']['resolution_y'] = $thisfile_real_chunks_currentchunk_videoinfo['height'];
|
||||||
|
$info['video']['frame_rate'] = (float) $thisfile_real_chunks_currentchunk_videoinfo['frames_per_second'];
|
||||||
|
$info['video']['codec'] = $thisfile_real_chunks_currentchunk_videoinfo['codec'];
|
||||||
|
$info['video']['bits_per_sample'] = $thisfile_real_chunks_currentchunk_videoinfo['bits_per_sample'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'audio/x-pn-realaudio':
|
||||||
|
case 'audio/x-pn-multirate-realaudio':
|
||||||
|
$this->ParseOldRAheader($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk['parsed_audio_data']);
|
||||||
|
|
||||||
|
$info['audio']['sample_rate'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['sample_rate'];
|
||||||
|
$info['audio']['bits_per_sample'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['bits_per_sample'];
|
||||||
|
$info['audio']['channels'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['channels'];
|
||||||
|
if (!empty($info['audio']['dataformat'])) {
|
||||||
|
foreach ($info['audio'] as $key => $value) {
|
||||||
|
if ($key != 'streams') {
|
||||||
|
$info['audio']['streams'][$thisfile_real_chunks_currentchunk['stream_number']][$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'logical-fileinfo':
|
||||||
|
// shortcut
|
||||||
|
$thisfile_real_chunks_currentchunk['logical_fileinfo'] = array();
|
||||||
|
$thisfile_real_chunks_currentchunk_logicalfileinfo = &$thisfile_real_chunks_currentchunk['logical_fileinfo'];
|
||||||
|
|
||||||
|
$thisfile_real_chunks_currentchunk_logicalfileinfo_offset = 0;
|
||||||
|
$thisfile_real_chunks_currentchunk_logicalfileinfo['logical_fileinfo_length'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4));
|
||||||
|
$thisfile_real_chunks_currentchunk_logicalfileinfo_offset += 4;
|
||||||
|
|
||||||
|
//$thisfile_real_chunks_currentchunk_logicalfileinfo['unknown1'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4));
|
||||||
|
$thisfile_real_chunks_currentchunk_logicalfileinfo_offset += 4;
|
||||||
|
|
||||||
|
$thisfile_real_chunks_currentchunk_logicalfileinfo['num_tags'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4));
|
||||||
|
$thisfile_real_chunks_currentchunk_logicalfileinfo_offset += 4;
|
||||||
|
|
||||||
|
//$thisfile_real_chunks_currentchunk_logicalfileinfo['unknown2'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4));
|
||||||
|
$thisfile_real_chunks_currentchunk_logicalfileinfo_offset += 4;
|
||||||
|
|
||||||
|
//$thisfile_real_chunks_currentchunk_logicalfileinfo['d'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 1));
|
||||||
|
|
||||||
|
//$thisfile_real_chunks_currentchunk_logicalfileinfo['one_type'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4));
|
||||||
|
//$thisfile_real_chunks_currentchunk_logicalfileinfo_thislength = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 4 + $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 2));
|
||||||
|
//$thisfile_real_chunks_currentchunk_logicalfileinfo['one'] = substr($thisfile_real_chunks_currentchunk_typespecificdata, 6 + $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, $thisfile_real_chunks_currentchunk_logicalfileinfo_thislength);
|
||||||
|
//$thisfile_real_chunks_currentchunk_logicalfileinfo_offset += (6 + $thisfile_real_chunks_currentchunk_logicalfileinfo_thislength);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (empty($info['playtime_seconds'])) {
|
||||||
|
$info['playtime_seconds'] = max($info['playtime_seconds'], ($thisfile_real_chunks_currentchunk['duration'] + $thisfile_real_chunks_currentchunk['start_time']) / 1000);
|
||||||
|
}
|
||||||
|
if ($thisfile_real_chunks_currentchunk['duration'] > 0) {
|
||||||
|
switch ($thisfile_real_chunks_currentchunk['mime_type']) {
|
||||||
|
case 'audio/x-pn-realaudio':
|
||||||
|
case 'audio/x-pn-multirate-realaudio':
|
||||||
|
$info['audio']['bitrate'] = (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate'];
|
||||||
|
$info['audio']['codec'] = $this->RealAudioCodecFourCClookup($thisfile_real_chunks_currentchunk['parsed_audio_data']['fourcc'], $info['audio']['bitrate']);
|
||||||
|
$info['audio']['dataformat'] = 'real';
|
||||||
|
$info['audio']['lossless'] = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'video/x-pn-realvideo':
|
||||||
|
case 'video/x-pn-multirate-realvideo':
|
||||||
|
$info['video']['bitrate'] = (isset($info['video']['bitrate']) ? $info['video']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate'];
|
||||||
|
$info['video']['bitrate_mode'] = 'cbr';
|
||||||
|
$info['video']['dataformat'] = 'real';
|
||||||
|
$info['video']['lossless'] = false;
|
||||||
|
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'audio/x-ralf-mpeg4-generic':
|
||||||
|
$info['audio']['bitrate'] = (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate'];
|
||||||
|
$info['audio']['codec'] = 'RealAudio Lossless';
|
||||||
|
$info['audio']['dataformat'] = 'real';
|
||||||
|
$info['audio']['lossless'] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$info['bitrate'] = (isset($info['video']['bitrate']) ? $info['video']['bitrate'] : 0) + (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'CONT': // Content Description Header (text comments)
|
||||||
|
$thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
if ($thisfile_real_chunks_currentchunk['object_version'] == 0) {
|
||||||
|
$thisfile_real_chunks_currentchunk['title_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_real_chunks_currentchunk['title'] = (string) substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['title_len']);
|
||||||
|
$offset += $thisfile_real_chunks_currentchunk['title_len'];
|
||||||
|
|
||||||
|
$thisfile_real_chunks_currentchunk['artist_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_real_chunks_currentchunk['artist'] = (string) substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['artist_len']);
|
||||||
|
$offset += $thisfile_real_chunks_currentchunk['artist_len'];
|
||||||
|
|
||||||
|
$thisfile_real_chunks_currentchunk['copyright_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_real_chunks_currentchunk['copyright'] = (string) substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['copyright_len']);
|
||||||
|
$offset += $thisfile_real_chunks_currentchunk['copyright_len'];
|
||||||
|
|
||||||
|
$thisfile_real_chunks_currentchunk['comment_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_real_chunks_currentchunk['comment'] = (string) substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['comment_len']);
|
||||||
|
$offset += $thisfile_real_chunks_currentchunk['comment_len'];
|
||||||
|
|
||||||
|
|
||||||
|
$commentkeystocopy = array('title'=>'title', 'artist'=>'artist', 'copyright'=>'copyright', 'comment'=>'comment');
|
||||||
|
foreach ($commentkeystocopy as $key => $val) {
|
||||||
|
if ($thisfile_real_chunks_currentchunk[$key]) {
|
||||||
|
$info['real']['comments'][$val][] = trim($thisfile_real_chunks_currentchunk[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'DATA': // Data Chunk Header
|
||||||
|
// do nothing
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'INDX': // Index Section Header
|
||||||
|
$thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
if ($thisfile_real_chunks_currentchunk['object_version'] == 0) {
|
||||||
|
$thisfile_real_chunks_currentchunk['num_indices'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_real_chunks_currentchunk['stream_number'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_real_chunks_currentchunk['next_index_header'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
if ($thisfile_real_chunks_currentchunk['next_index_header'] == 0) {
|
||||||
|
// last index chunk found, ignore rest of file
|
||||||
|
break 2;
|
||||||
|
} else {
|
||||||
|
// non-last index chunk, seek to next index chunk (skipping actual index data)
|
||||||
|
fseek($this->getid3->fp, $thisfile_real_chunks_currentchunk['next_index_header'], SEEK_SET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['warning'][] = 'Unhandled RealMedia chunk "'.$ChunkName.'" at offset '.$thisfile_real_chunks_currentchunk['offset'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$ChunkCounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($info['audio']['streams'])) {
|
||||||
|
$info['audio']['bitrate'] = 0;
|
||||||
|
foreach ($info['audio']['streams'] as $key => $valuearray) {
|
||||||
|
$info['audio']['bitrate'] += $valuearray['bitrate'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ParseOldRAheader($OldRAheaderData, &$ParsedArray) {
|
||||||
|
// http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html
|
||||||
|
|
||||||
|
$ParsedArray = array();
|
||||||
|
$ParsedArray['magic'] = substr($OldRAheaderData, 0, 4);
|
||||||
|
if ($ParsedArray['magic'] != '.ra'."\xFD") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$ParsedArray['version1'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 4, 2));
|
||||||
|
|
||||||
|
if ($ParsedArray['version1'] < 3) {
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} elseif ($ParsedArray['version1'] == 3) {
|
||||||
|
|
||||||
|
$ParsedArray['fourcc1'] = '.ra3';
|
||||||
|
$ParsedArray['bits_per_sample'] = 16; // hard-coded for old versions?
|
||||||
|
$ParsedArray['sample_rate'] = 8000; // hard-coded for old versions?
|
||||||
|
|
||||||
|
$ParsedArray['header_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 6, 2));
|
||||||
|
$ParsedArray['channels'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 8, 2)); // always 1 (?)
|
||||||
|
//$ParsedArray['unknown1'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 10, 2));
|
||||||
|
//$ParsedArray['unknown2'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 12, 2));
|
||||||
|
//$ParsedArray['unknown3'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 14, 2));
|
||||||
|
$ParsedArray['bytes_per_minute'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 16, 2));
|
||||||
|
$ParsedArray['audio_bytes'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 18, 4));
|
||||||
|
$ParsedArray['comments_raw'] = substr($OldRAheaderData, 22, $ParsedArray['header_size'] - 22 + 1); // not including null terminator
|
||||||
|
|
||||||
|
$commentoffset = 0;
|
||||||
|
$commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1));
|
||||||
|
$ParsedArray['comments']['title'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength);
|
||||||
|
$commentoffset += $commentlength;
|
||||||
|
|
||||||
|
$commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1));
|
||||||
|
$ParsedArray['comments']['artist'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength);
|
||||||
|
$commentoffset += $commentlength;
|
||||||
|
|
||||||
|
$commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1));
|
||||||
|
$ParsedArray['comments']['copyright'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength);
|
||||||
|
$commentoffset += $commentlength;
|
||||||
|
|
||||||
|
$commentoffset++; // final null terminator (?)
|
||||||
|
$commentoffset++; // fourcc length (?) should be 4
|
||||||
|
$ParsedArray['fourcc'] = substr($OldRAheaderData, 23 + $commentoffset, 4);
|
||||||
|
|
||||||
|
} elseif ($ParsedArray['version1'] <= 5) {
|
||||||
|
|
||||||
|
//$ParsedArray['unknown1'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 6, 2));
|
||||||
|
$ParsedArray['fourcc1'] = substr($OldRAheaderData, 8, 4);
|
||||||
|
$ParsedArray['file_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 12, 4));
|
||||||
|
$ParsedArray['version2'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 16, 2));
|
||||||
|
$ParsedArray['header_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 18, 4));
|
||||||
|
$ParsedArray['codec_flavor_id'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 22, 2));
|
||||||
|
$ParsedArray['coded_frame_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 24, 4));
|
||||||
|
$ParsedArray['audio_bytes'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 28, 4));
|
||||||
|
$ParsedArray['bytes_per_minute'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 32, 4));
|
||||||
|
//$ParsedArray['unknown5'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 36, 4));
|
||||||
|
$ParsedArray['sub_packet_h'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 40, 2));
|
||||||
|
$ParsedArray['frame_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 42, 2));
|
||||||
|
$ParsedArray['sub_packet_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 44, 2));
|
||||||
|
//$ParsedArray['unknown6'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 46, 2));
|
||||||
|
|
||||||
|
switch ($ParsedArray['version1']) {
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
$ParsedArray['sample_rate'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 48, 2));
|
||||||
|
//$ParsedArray['unknown8'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 50, 2));
|
||||||
|
$ParsedArray['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 52, 2));
|
||||||
|
$ParsedArray['channels'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 54, 2));
|
||||||
|
$ParsedArray['length_fourcc2'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 56, 1));
|
||||||
|
$ParsedArray['fourcc2'] = substr($OldRAheaderData, 57, 4);
|
||||||
|
$ParsedArray['length_fourcc3'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 61, 1));
|
||||||
|
$ParsedArray['fourcc3'] = substr($OldRAheaderData, 62, 4);
|
||||||
|
//$ParsedArray['unknown9'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 66, 1));
|
||||||
|
//$ParsedArray['unknown10'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 67, 2));
|
||||||
|
$ParsedArray['comments_raw'] = substr($OldRAheaderData, 69, $ParsedArray['header_size'] - 69 + 16);
|
||||||
|
|
||||||
|
$commentoffset = 0;
|
||||||
|
$commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1));
|
||||||
|
$ParsedArray['comments']['title'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength);
|
||||||
|
$commentoffset += $commentlength;
|
||||||
|
|
||||||
|
$commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1));
|
||||||
|
$ParsedArray['comments']['artist'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength);
|
||||||
|
$commentoffset += $commentlength;
|
||||||
|
|
||||||
|
$commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1));
|
||||||
|
$ParsedArray['comments']['copyright'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength);
|
||||||
|
$commentoffset += $commentlength;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
$ParsedArray['sample_rate'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 48, 4));
|
||||||
|
$ParsedArray['sample_rate2'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 52, 4));
|
||||||
|
$ParsedArray['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 56, 4));
|
||||||
|
$ParsedArray['channels'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 60, 2));
|
||||||
|
$ParsedArray['genr'] = substr($OldRAheaderData, 62, 4);
|
||||||
|
$ParsedArray['fourcc3'] = substr($OldRAheaderData, 66, 4);
|
||||||
|
$ParsedArray['comments'] = array();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$ParsedArray['fourcc'] = $ParsedArray['fourcc3'];
|
||||||
|
|
||||||
|
}
|
||||||
|
foreach ($ParsedArray['comments'] as $key => $value) {
|
||||||
|
if ($ParsedArray['comments'][$key][0] === false) {
|
||||||
|
$ParsedArray['comments'][$key][0] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RealAudioCodecFourCClookup($fourcc, $bitrate) {
|
||||||
|
static $RealAudioCodecFourCClookup = array();
|
||||||
|
if (empty($RealAudioCodecFourCClookup)) {
|
||||||
|
// http://www.its.msstate.edu/net/real/reports/config/tags.stats
|
||||||
|
// http://www.freelists.org/archives/matroska-devel/06-2003/fullthread18.html
|
||||||
|
|
||||||
|
$RealAudioCodecFourCClookup['14_4'][8000] = 'RealAudio v2 (14.4kbps)';
|
||||||
|
$RealAudioCodecFourCClookup['14.4'][8000] = 'RealAudio v2 (14.4kbps)';
|
||||||
|
$RealAudioCodecFourCClookup['lpcJ'][8000] = 'RealAudio v2 (14.4kbps)';
|
||||||
|
$RealAudioCodecFourCClookup['28_8'][15200] = 'RealAudio v2 (28.8kbps)';
|
||||||
|
$RealAudioCodecFourCClookup['28.8'][15200] = 'RealAudio v2 (28.8kbps)';
|
||||||
|
$RealAudioCodecFourCClookup['sipr'][4933] = 'RealAudio v4 (5kbps Voice)';
|
||||||
|
$RealAudioCodecFourCClookup['sipr'][6444] = 'RealAudio v4 (6.5kbps Voice)';
|
||||||
|
$RealAudioCodecFourCClookup['sipr'][8444] = 'RealAudio v4 (8.5kbps Voice)';
|
||||||
|
$RealAudioCodecFourCClookup['sipr'][16000] = 'RealAudio v4 (16kbps Wideband)';
|
||||||
|
$RealAudioCodecFourCClookup['dnet'][8000] = 'RealAudio v3 (8kbps Music)';
|
||||||
|
$RealAudioCodecFourCClookup['dnet'][16000] = 'RealAudio v3 (16kbps Music Low Response)';
|
||||||
|
$RealAudioCodecFourCClookup['dnet'][15963] = 'RealAudio v3 (16kbps Music Mid/High Response)';
|
||||||
|
$RealAudioCodecFourCClookup['dnet'][20000] = 'RealAudio v3 (20kbps Music Stereo)';
|
||||||
|
$RealAudioCodecFourCClookup['dnet'][32000] = 'RealAudio v3 (32kbps Music Mono)';
|
||||||
|
$RealAudioCodecFourCClookup['dnet'][31951] = 'RealAudio v3 (32kbps Music Stereo)';
|
||||||
|
$RealAudioCodecFourCClookup['dnet'][39965] = 'RealAudio v3 (40kbps Music Mono)';
|
||||||
|
$RealAudioCodecFourCClookup['dnet'][40000] = 'RealAudio v3 (40kbps Music Stereo)';
|
||||||
|
$RealAudioCodecFourCClookup['dnet'][79947] = 'RealAudio v3 (80kbps Music Mono)';
|
||||||
|
$RealAudioCodecFourCClookup['dnet'][80000] = 'RealAudio v3 (80kbps Music Stereo)';
|
||||||
|
|
||||||
|
$RealAudioCodecFourCClookup['dnet'][0] = 'RealAudio v3';
|
||||||
|
$RealAudioCodecFourCClookup['sipr'][0] = 'RealAudio v4';
|
||||||
|
$RealAudioCodecFourCClookup['cook'][0] = 'RealAudio G2';
|
||||||
|
$RealAudioCodecFourCClookup['atrc'][0] = 'RealAudio 8';
|
||||||
|
}
|
||||||
|
$roundbitrate = intval(round($bitrate));
|
||||||
|
if (isset($RealAudioCodecFourCClookup[$fourcc][$roundbitrate])) {
|
||||||
|
return $RealAudioCodecFourCClookup[$fourcc][$roundbitrate];
|
||||||
|
} elseif (isset($RealAudioCodecFourCClookup[$fourcc][0])) {
|
||||||
|
return $RealAudioCodecFourCClookup[$fourcc][0];
|
||||||
|
}
|
||||||
|
return $fourcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
2409
app/library/getid3/module.audio-video.riff.php
Normal file
2409
app/library/getid3/module.audio-video.riff.php
Normal file
File diff suppressed because it is too large
Load diff
142
app/library/getid3/module.audio-video.swf.php
Normal file
142
app/library/getid3/module.audio-video.swf.php
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio-video.swf.php //
|
||||||
|
// module for analyzing Shockwave Flash files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_swf extends getid3_handler
|
||||||
|
{
|
||||||
|
var $ReturnAllTagData = false;
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'swf';
|
||||||
|
$info['video']['dataformat'] = 'swf';
|
||||||
|
|
||||||
|
// http://www.openswf.org/spec/SWFfileformat.html
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
|
||||||
|
$SWFfileData = fread($this->getid3->fp, $info['avdataend'] - $info['avdataoffset']); // 8 + 2 + 2 + max(9) bytes NOT including Frame_Size RECT data
|
||||||
|
|
||||||
|
$info['swf']['header']['signature'] = substr($SWFfileData, 0, 3);
|
||||||
|
switch ($info['swf']['header']['signature']) {
|
||||||
|
case 'FWS':
|
||||||
|
$info['swf']['header']['compressed'] = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'CWS':
|
||||||
|
$info['swf']['header']['compressed'] = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['error'][] = 'Expecting "FWS" or "CWS" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['swf']['header']['signature']).'"';
|
||||||
|
unset($info['swf']);
|
||||||
|
unset($info['fileformat']);
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$info['swf']['header']['version'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 3, 1));
|
||||||
|
$info['swf']['header']['length'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 4, 4));
|
||||||
|
|
||||||
|
if ($info['swf']['header']['compressed']) {
|
||||||
|
$SWFHead = substr($SWFfileData, 0, 8);
|
||||||
|
$SWFfileData = substr($SWFfileData, 8);
|
||||||
|
if ($decompressed = @gzuncompress($SWFfileData)) {
|
||||||
|
$SWFfileData = $SWFHead.$decompressed;
|
||||||
|
} else {
|
||||||
|
$info['error'][] = 'Error decompressing compressed SWF data ('.strlen($SWFfileData).' bytes compressed, should be '.($info['swf']['header']['length'] - 8).' bytes uncompressed)';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$FrameSizeBitsPerValue = (ord(substr($SWFfileData, 8, 1)) & 0xF8) >> 3;
|
||||||
|
$FrameSizeDataLength = ceil((5 + (4 * $FrameSizeBitsPerValue)) / 8);
|
||||||
|
$FrameSizeDataString = str_pad(decbin(ord(substr($SWFfileData, 8, 1)) & 0x07), 3, '0', STR_PAD_LEFT);
|
||||||
|
for ($i = 1; $i < $FrameSizeDataLength; $i++) {
|
||||||
|
$FrameSizeDataString .= str_pad(decbin(ord(substr($SWFfileData, 8 + $i, 1))), 8, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
list($X1, $X2, $Y1, $Y2) = explode("\n", wordwrap($FrameSizeDataString, $FrameSizeBitsPerValue, "\n", 1));
|
||||||
|
$info['swf']['header']['frame_width'] = getid3_lib::Bin2Dec($X2);
|
||||||
|
$info['swf']['header']['frame_height'] = getid3_lib::Bin2Dec($Y2);
|
||||||
|
|
||||||
|
// http://www-lehre.informatik.uni-osnabrueck.de/~fbstark/diplom/docs/swf/Flash_Uncovered.htm
|
||||||
|
// Next in the header is the frame rate, which is kind of weird.
|
||||||
|
// It is supposed to be stored as a 16bit integer, but the first byte
|
||||||
|
// (or last depending on how you look at it) is completely ignored.
|
||||||
|
// Example: 0x000C -> 0x0C -> 12 So the frame rate is 12 fps.
|
||||||
|
|
||||||
|
// Byte at (8 + $FrameSizeDataLength) is always zero and ignored
|
||||||
|
$info['swf']['header']['frame_rate'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 9 + $FrameSizeDataLength, 1));
|
||||||
|
$info['swf']['header']['frame_count'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 10 + $FrameSizeDataLength, 2));
|
||||||
|
|
||||||
|
$info['video']['frame_rate'] = $info['swf']['header']['frame_rate'];
|
||||||
|
$info['video']['resolution_x'] = intval(round($info['swf']['header']['frame_width'] / 20));
|
||||||
|
$info['video']['resolution_y'] = intval(round($info['swf']['header']['frame_height'] / 20));
|
||||||
|
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||||
|
|
||||||
|
if (($info['swf']['header']['frame_count'] > 0) && ($info['swf']['header']['frame_rate'] > 0)) {
|
||||||
|
$info['playtime_seconds'] = $info['swf']['header']['frame_count'] / $info['swf']['header']['frame_rate'];
|
||||||
|
}
|
||||||
|
//echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'<br>';
|
||||||
|
|
||||||
|
|
||||||
|
// SWF tags
|
||||||
|
|
||||||
|
$CurrentOffset = 12 + $FrameSizeDataLength;
|
||||||
|
$SWFdataLength = strlen($SWFfileData);
|
||||||
|
|
||||||
|
while ($CurrentOffset < $SWFdataLength) {
|
||||||
|
//echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'<br>';
|
||||||
|
|
||||||
|
$TagIDTagLength = getid3_lib::LittleEndian2Int(substr($SWFfileData, $CurrentOffset, 2));
|
||||||
|
$TagID = ($TagIDTagLength & 0xFFFC) >> 6;
|
||||||
|
$TagLength = ($TagIDTagLength & 0x003F);
|
||||||
|
$CurrentOffset += 2;
|
||||||
|
if ($TagLength == 0x3F) {
|
||||||
|
$TagLength = getid3_lib::LittleEndian2Int(substr($SWFfileData, $CurrentOffset, 4));
|
||||||
|
$CurrentOffset += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($TagData);
|
||||||
|
$TagData['offset'] = $CurrentOffset;
|
||||||
|
$TagData['size'] = $TagLength;
|
||||||
|
$TagData['id'] = $TagID;
|
||||||
|
$TagData['data'] = substr($SWFfileData, $CurrentOffset, $TagLength);
|
||||||
|
switch ($TagID) {
|
||||||
|
case 0: // end of movie
|
||||||
|
break 2;
|
||||||
|
|
||||||
|
case 9: // Set background color
|
||||||
|
//$info['swf']['tags'][] = $TagData;
|
||||||
|
$info['swf']['bgcolor'] = strtoupper(str_pad(dechex(getid3_lib::BigEndian2Int($TagData['data'])), 6, '0', STR_PAD_LEFT));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if ($this->ReturnAllTagData) {
|
||||||
|
$info['swf']['tags'][] = $TagData;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$CurrentOffset += $TagLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
59
app/library/getid3/module.audio.aa.php
Normal file
59
app/library/getid3/module.audio.aa.php
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.aa.php //
|
||||||
|
// module for analyzing Audible Audiobook files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_aa extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$AAheader = fread($this->getid3->fp, 8);
|
||||||
|
|
||||||
|
$magic = "\x57\x90\x75\x36";
|
||||||
|
if (substr($AAheader, 4, 4) != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($AAheader, 4, 4)).'"';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$info['aa'] = array();
|
||||||
|
$thisfile_au = &$info['aa'];
|
||||||
|
|
||||||
|
$info['fileformat'] = 'aa';
|
||||||
|
$info['audio']['dataformat'] = 'aa';
|
||||||
|
$info['audio']['bitrate_mode'] = 'cbr'; // is it?
|
||||||
|
$thisfile_au['encoding'] = 'ISO-8859-1';
|
||||||
|
|
||||||
|
$thisfile_au['filesize'] = getid3_lib::BigEndian2Int(substr($AUheader, 0, 4));
|
||||||
|
if ($thisfile_au['filesize'] > ($info['avdataend'] - $info['avdataoffset'])) {
|
||||||
|
$info['warning'][] = 'Possible truncated file - expecting "'.$thisfile_au['filesize'].'" bytes of data, only found '.($info['avdataend'] - $info['avdataoffset']).' bytes"';
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['audio']['bits_per_sample'] = 16; // is it?
|
||||||
|
$info['audio']['sample_rate'] = $thisfile_au['sample_rate'];
|
||||||
|
$info['audio']['channels'] = $thisfile_au['channels'];
|
||||||
|
|
||||||
|
//$info['playtime_seconds'] = 0;
|
||||||
|
//$info['audio']['bitrate'] = 0;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
515
app/library/getid3/module.audio.aac.php
Normal file
515
app/library/getid3/module.audio.aac.php
Normal file
|
@ -0,0 +1,515 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.aac.php //
|
||||||
|
// module for analyzing AAC Audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_aac extends getid3_handler
|
||||||
|
{
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
if (fread($this->getid3->fp, 4) == 'ADIF') {
|
||||||
|
$this->getAACADIFheaderFilepointer();
|
||||||
|
} else {
|
||||||
|
$this->getAACADTSheaderFilepointer();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function getAACADIFheaderFilepointer() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
$info['fileformat'] = 'aac';
|
||||||
|
$info['audio']['dataformat'] = 'aac';
|
||||||
|
$info['audio']['lossless'] = false;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$AACheader = fread($this->getid3->fp, 1024);
|
||||||
|
$offset = 0;
|
||||||
|
|
||||||
|
if (substr($AACheader, 0, 4) == 'ADIF') {
|
||||||
|
|
||||||
|
// http://faac.sourceforge.net/wiki/index.php?page=ADIF
|
||||||
|
|
||||||
|
// http://libmpeg.org/mpeg4/doc/w2203tfs.pdf
|
||||||
|
// adif_header() {
|
||||||
|
// adif_id 32
|
||||||
|
// copyright_id_present 1
|
||||||
|
// if( copyright_id_present )
|
||||||
|
// copyright_id 72
|
||||||
|
// original_copy 1
|
||||||
|
// home 1
|
||||||
|
// bitstream_type 1
|
||||||
|
// bitrate 23
|
||||||
|
// num_program_config_elements 4
|
||||||
|
// for (i = 0; i < num_program_config_elements + 1; i++ ) {
|
||||||
|
// if( bitstream_type == '0' )
|
||||||
|
// adif_buffer_fullness 20
|
||||||
|
// program_config_element()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
$AACheaderBitstream = getid3_lib::BigEndian2Bin($AACheader);
|
||||||
|
$bitoffset = 0;
|
||||||
|
|
||||||
|
$info['aac']['header_type'] = 'ADIF';
|
||||||
|
$bitoffset += 32;
|
||||||
|
$info['aac']['header']['mpeg_version'] = 4;
|
||||||
|
|
||||||
|
$info['aac']['header']['copyright'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
|
||||||
|
$bitoffset += 1;
|
||||||
|
if ($info['aac']['header']['copyright']) {
|
||||||
|
$info['aac']['header']['copyright_id'] = getid3_lib::Bin2String(substr($AACheaderBitstream, $bitoffset, 72));
|
||||||
|
$bitoffset += 72;
|
||||||
|
}
|
||||||
|
$info['aac']['header']['original_copy'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
|
||||||
|
$bitoffset += 1;
|
||||||
|
$info['aac']['header']['home'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
|
||||||
|
$bitoffset += 1;
|
||||||
|
$info['aac']['header']['is_vbr'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
|
||||||
|
$bitoffset += 1;
|
||||||
|
if ($info['aac']['header']['is_vbr']) {
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
$info['aac']['header']['bitrate_max'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23));
|
||||||
|
$bitoffset += 23;
|
||||||
|
} else {
|
||||||
|
$info['audio']['bitrate_mode'] = 'cbr';
|
||||||
|
$info['aac']['header']['bitrate'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23));
|
||||||
|
$bitoffset += 23;
|
||||||
|
$info['audio']['bitrate'] = $info['aac']['header']['bitrate'];
|
||||||
|
}
|
||||||
|
if ($info['audio']['bitrate'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt AAC file: bitrate_audio == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['aac']['header']['num_program_configs'] = 1 + getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $info['aac']['header']['num_program_configs']; $i++) {
|
||||||
|
// http://www.audiocoding.com/wiki/index.php?page=program_config_element
|
||||||
|
|
||||||
|
// buffer_fullness 20
|
||||||
|
|
||||||
|
// element_instance_tag 4
|
||||||
|
// object_type 2
|
||||||
|
// sampling_frequency_index 4
|
||||||
|
// num_front_channel_elements 4
|
||||||
|
// num_side_channel_elements 4
|
||||||
|
// num_back_channel_elements 4
|
||||||
|
// num_lfe_channel_elements 2
|
||||||
|
// num_assoc_data_elements 3
|
||||||
|
// num_valid_cc_elements 4
|
||||||
|
// mono_mixdown_present 1
|
||||||
|
// mono_mixdown_element_number 4 if mono_mixdown_present == 1
|
||||||
|
// stereo_mixdown_present 1
|
||||||
|
// stereo_mixdown_element_number 4 if stereo_mixdown_present == 1
|
||||||
|
// matrix_mixdown_idx_present 1
|
||||||
|
// matrix_mixdown_idx 2 if matrix_mixdown_idx_present == 1
|
||||||
|
// pseudo_surround_enable 1 if matrix_mixdown_idx_present == 1
|
||||||
|
// for (i = 0; i < num_front_channel_elements; i++) {
|
||||||
|
// front_element_is_cpe[i] 1
|
||||||
|
// front_element_tag_select[i] 4
|
||||||
|
// }
|
||||||
|
// for (i = 0; i < num_side_channel_elements; i++) {
|
||||||
|
// side_element_is_cpe[i] 1
|
||||||
|
// side_element_tag_select[i] 4
|
||||||
|
// }
|
||||||
|
// for (i = 0; i < num_back_channel_elements; i++) {
|
||||||
|
// back_element_is_cpe[i] 1
|
||||||
|
// back_element_tag_select[i] 4
|
||||||
|
// }
|
||||||
|
// for (i = 0; i < num_lfe_channel_elements; i++) {
|
||||||
|
// lfe_element_tag_select[i] 4
|
||||||
|
// }
|
||||||
|
// for (i = 0; i < num_assoc_data_elements; i++) {
|
||||||
|
// assoc_data_element_tag_select[i] 4
|
||||||
|
// }
|
||||||
|
// for (i = 0; i < num_valid_cc_elements; i++) {
|
||||||
|
// cc_element_is_ind_sw[i] 1
|
||||||
|
// valid_cc_element_tag_select[i] 4
|
||||||
|
// }
|
||||||
|
// byte_alignment() VAR
|
||||||
|
// comment_field_bytes 8
|
||||||
|
// for (i = 0; i < comment_field_bytes; i++) {
|
||||||
|
// comment_field_data[i] 8
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!$info['aac']['header']['is_vbr']) {
|
||||||
|
$info['aac']['program_configs'][$i]['buffer_fullness'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 20));
|
||||||
|
$bitoffset += 20;
|
||||||
|
}
|
||||||
|
$info['aac']['program_configs'][$i]['element_instance_tag'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
$info['aac']['program_configs'][$i]['object_type'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
|
||||||
|
$bitoffset += 2;
|
||||||
|
$info['aac']['program_configs'][$i]['sampling_frequency_index'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
$info['aac']['program_configs'][$i]['num_front_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
$info['aac']['program_configs'][$i]['num_side_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
$info['aac']['program_configs'][$i]['num_back_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
$info['aac']['program_configs'][$i]['num_lfe_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
|
||||||
|
$bitoffset += 2;
|
||||||
|
$info['aac']['program_configs'][$i]['num_assoc_data_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 3));
|
||||||
|
$bitoffset += 3;
|
||||||
|
$info['aac']['program_configs'][$i]['num_valid_cc_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
$info['aac']['program_configs'][$i]['mono_mixdown_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||||
|
$bitoffset += 1;
|
||||||
|
if ($info['aac']['program_configs'][$i]['mono_mixdown_present']) {
|
||||||
|
$info['aac']['program_configs'][$i]['mono_mixdown_element_number'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
}
|
||||||
|
$info['aac']['program_configs'][$i]['stereo_mixdown_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||||
|
$bitoffset += 1;
|
||||||
|
if ($info['aac']['program_configs'][$i]['stereo_mixdown_present']) {
|
||||||
|
$info['aac']['program_configs'][$i]['stereo_mixdown_element_number'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
}
|
||||||
|
$info['aac']['program_configs'][$i]['matrix_mixdown_idx_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||||
|
$bitoffset += 1;
|
||||||
|
if ($info['aac']['program_configs'][$i]['matrix_mixdown_idx_present']) {
|
||||||
|
$info['aac']['program_configs'][$i]['matrix_mixdown_idx'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
|
||||||
|
$bitoffset += 2;
|
||||||
|
$info['aac']['program_configs'][$i]['pseudo_surround_enable'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||||
|
$bitoffset += 1;
|
||||||
|
}
|
||||||
|
for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_front_channel_elements']; $j++) {
|
||||||
|
$info['aac']['program_configs'][$i]['front_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||||
|
$bitoffset += 1;
|
||||||
|
$info['aac']['program_configs'][$i]['front_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
}
|
||||||
|
for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_side_channel_elements']; $j++) {
|
||||||
|
$info['aac']['program_configs'][$i]['side_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||||
|
$bitoffset += 1;
|
||||||
|
$info['aac']['program_configs'][$i]['side_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
}
|
||||||
|
for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_back_channel_elements']; $j++) {
|
||||||
|
$info['aac']['program_configs'][$i]['back_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||||
|
$bitoffset += 1;
|
||||||
|
$info['aac']['program_configs'][$i]['back_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
}
|
||||||
|
for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_lfe_channel_elements']; $j++) {
|
||||||
|
$info['aac']['program_configs'][$i]['lfe_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
}
|
||||||
|
for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_assoc_data_elements']; $j++) {
|
||||||
|
$info['aac']['program_configs'][$i]['assoc_data_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
}
|
||||||
|
for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_valid_cc_elements']; $j++) {
|
||||||
|
$info['aac']['program_configs'][$i]['cc_element_is_ind_sw'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
|
||||||
|
$bitoffset += 1;
|
||||||
|
$info['aac']['program_configs'][$i]['valid_cc_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
|
||||||
|
$bitoffset += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bitoffset = ceil($bitoffset / 8) * 8;
|
||||||
|
|
||||||
|
$info['aac']['program_configs'][$i]['comment_field_bytes'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 8));
|
||||||
|
$bitoffset += 8;
|
||||||
|
$info['aac']['program_configs'][$i]['comment_field'] = getid3_lib::Bin2String(substr($AACheaderBitstream, $bitoffset, 8 * $info['aac']['program_configs'][$i]['comment_field_bytes']));
|
||||||
|
$bitoffset += 8 * $info['aac']['program_configs'][$i]['comment_field_bytes'];
|
||||||
|
|
||||||
|
|
||||||
|
$info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['program_configs'][$i]['object_type'], $info['aac']['header']['mpeg_version']);
|
||||||
|
$info['aac']['program_configs'][$i]['sampling_frequency'] = self::AACsampleRateLookup($info['aac']['program_configs'][$i]['sampling_frequency_index']);
|
||||||
|
$info['audio']['sample_rate'] = $info['aac']['program_configs'][$i]['sampling_frequency'];
|
||||||
|
$info['audio']['channels'] = self::AACchannelCountCalculate($info['aac']['program_configs'][$i]);
|
||||||
|
if ($info['aac']['program_configs'][$i]['comment_field']) {
|
||||||
|
$info['aac']['comments'][] = $info['aac']['program_configs'][$i]['comment_field'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate'];
|
||||||
|
|
||||||
|
$info['audio']['encoder_options'] = $info['aac']['header_type'].' '.$info['aac']['header']['profile'];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['aac']);
|
||||||
|
$info['error'][] = 'AAC-ADIF synch not found at offset '.$info['avdataoffset'].' (expected "ADIF", found "'.substr($AACheader, 0, 4).'" instead)';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getAACADTSheaderFilepointer($MaxFramesToScan=1000000, $ReturnExtendedInfo=false) {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
// based loosely on code from AACfile by Jurgen Faul <jfaulØgmx.de>
|
||||||
|
// http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html
|
||||||
|
|
||||||
|
|
||||||
|
// http://faac.sourceforge.net/wiki/index.php?page=ADTS // dead link
|
||||||
|
// http://wiki.multimedia.cx/index.php?title=ADTS
|
||||||
|
|
||||||
|
// * ADTS Fixed Header: these don't change from frame to frame
|
||||||
|
// syncword 12 always: '111111111111'
|
||||||
|
// ID 1 0: MPEG-4, 1: MPEG-2
|
||||||
|
// MPEG layer 2 If you send AAC in MPEG-TS, set to 0
|
||||||
|
// protection_absent 1 0: CRC present; 1: no CRC
|
||||||
|
// profile 2 0: AAC Main; 1: AAC LC (Low Complexity); 2: AAC SSR (Scalable Sample Rate); 3: AAC LTP (Long Term Prediction)
|
||||||
|
// sampling_frequency_index 4 15 not allowed
|
||||||
|
// private_bit 1 usually 0
|
||||||
|
// channel_configuration 3
|
||||||
|
// original/copy 1 0: original; 1: copy
|
||||||
|
// home 1 usually 0
|
||||||
|
// emphasis 2 only if ID == 0 (ie MPEG-4) // not present in some documentation?
|
||||||
|
|
||||||
|
// * ADTS Variable Header: these can change from frame to frame
|
||||||
|
// copyright_identification_bit 1
|
||||||
|
// copyright_identification_start 1
|
||||||
|
// aac_frame_length 13 length of the frame including header (in bytes)
|
||||||
|
// adts_buffer_fullness 11 0x7FF indicates VBR
|
||||||
|
// no_raw_data_blocks_in_frame 2
|
||||||
|
|
||||||
|
// * ADTS Error check
|
||||||
|
// crc_check 16 only if protection_absent == 0
|
||||||
|
|
||||||
|
$byteoffset = $info['avdataoffset'];
|
||||||
|
$framenumber = 0;
|
||||||
|
|
||||||
|
// Init bit pattern array
|
||||||
|
static $decbin = array();
|
||||||
|
|
||||||
|
// Populate $bindec
|
||||||
|
for ($i = 0; $i < 256; $i++) {
|
||||||
|
$decbin[chr($i)] = str_pad(decbin($i), 8, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to calculate bitrate below
|
||||||
|
$BitrateCache = array();
|
||||||
|
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// breaks out when end-of-file encountered, or invalid data found,
|
||||||
|
// or MaxFramesToScan frames have been scanned
|
||||||
|
|
||||||
|
if (!getid3_lib::intValueSupported($byteoffset)) {
|
||||||
|
$info['warning'][] = 'Unable to parse AAC file beyond '.ftell($this->getid3->fp).' (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fseek($this->getid3->fp, $byteoffset, SEEK_SET);
|
||||||
|
|
||||||
|
// First get substring
|
||||||
|
$substring = fread($this->getid3->fp, 9); // header is 7 bytes (or 9 if CRC is present)
|
||||||
|
$substringlength = strlen($substring);
|
||||||
|
if ($substringlength != 9) {
|
||||||
|
$info['error'][] = 'Failed to read 7 bytes at offset '.(ftell($this->getid3->fp) - $substringlength).' (only read '.$substringlength.' bytes)';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// this would be easier with 64-bit math, but split it up to allow for 32-bit:
|
||||||
|
$header1 = getid3_lib::BigEndian2Int(substr($substring, 0, 2));
|
||||||
|
$header2 = getid3_lib::BigEndian2Int(substr($substring, 2, 4));
|
||||||
|
$header3 = getid3_lib::BigEndian2Int(substr($substring, 6, 1));
|
||||||
|
|
||||||
|
$info['aac']['header']['raw']['syncword'] = ($header1 & 0xFFF0) >> 4;
|
||||||
|
if ($info['aac']['header']['raw']['syncword'] != 0x0FFF) {
|
||||||
|
$info['error'][] = 'Synch pattern (0x0FFF) not found at offset '.(ftell($this->getid3->fp) - $substringlength).' (found 0x0'.strtoupper(dechex($info['aac']['header']['raw']['syncword'])).' instead)';
|
||||||
|
//if ($info['fileformat'] == 'aac') {
|
||||||
|
// return true;
|
||||||
|
//}
|
||||||
|
unset($info['aac']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gather info for first frame only - this takes time to do 1000 times!
|
||||||
|
if ($framenumber == 0) {
|
||||||
|
$info['aac']['header_type'] = 'ADTS';
|
||||||
|
$info['fileformat'] = 'aac';
|
||||||
|
$info['audio']['dataformat'] = 'aac';
|
||||||
|
|
||||||
|
$info['aac']['header']['raw']['mpeg_version'] = ($header1 & 0x0008) >> 3;
|
||||||
|
$info['aac']['header']['raw']['mpeg_layer'] = ($header1 & 0x0006) >> 1;
|
||||||
|
$info['aac']['header']['raw']['protection_absent'] = ($header1 & 0x0001) >> 0;
|
||||||
|
|
||||||
|
$info['aac']['header']['raw']['profile_code'] = ($header2 & 0xC0000000) >> 30;
|
||||||
|
$info['aac']['header']['raw']['sample_rate_code'] = ($header2 & 0x3C000000) >> 26;
|
||||||
|
$info['aac']['header']['raw']['private_stream'] = ($header2 & 0x02000000) >> 25;
|
||||||
|
$info['aac']['header']['raw']['channels_code'] = ($header2 & 0x01C00000) >> 22;
|
||||||
|
$info['aac']['header']['raw']['original'] = ($header2 & 0x00200000) >> 21;
|
||||||
|
$info['aac']['header']['raw']['home'] = ($header2 & 0x00100000) >> 20;
|
||||||
|
$info['aac']['header']['raw']['copyright_stream'] = ($header2 & 0x00080000) >> 19;
|
||||||
|
$info['aac']['header']['raw']['copyright_start'] = ($header2 & 0x00040000) >> 18;
|
||||||
|
$info['aac']['header']['raw']['frame_length'] = ($header2 & 0x0003FFE0) >> 5;
|
||||||
|
|
||||||
|
$info['aac']['header']['mpeg_version'] = ($info['aac']['header']['raw']['mpeg_version'] ? 2 : 4);
|
||||||
|
$info['aac']['header']['crc_present'] = ($info['aac']['header']['raw']['protection_absent'] ? false: true);
|
||||||
|
$info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['header']['raw']['profile_code'], $info['aac']['header']['mpeg_version']);
|
||||||
|
$info['aac']['header']['sample_frequency'] = self::AACsampleRateLookup($info['aac']['header']['raw']['sample_rate_code']);
|
||||||
|
$info['aac']['header']['private'] = (bool) $info['aac']['header']['raw']['private_stream'];
|
||||||
|
$info['aac']['header']['original'] = (bool) $info['aac']['header']['raw']['original'];
|
||||||
|
$info['aac']['header']['home'] = (bool) $info['aac']['header']['raw']['home'];
|
||||||
|
$info['aac']['header']['channels'] = (($info['aac']['header']['raw']['channels_code'] == 7) ? 8 : $info['aac']['header']['raw']['channels_code']);
|
||||||
|
if ($ReturnExtendedInfo) {
|
||||||
|
$info['aac'][$framenumber]['copyright_id_bit'] = (bool) $info['aac']['header']['raw']['copyright_stream'];
|
||||||
|
$info['aac'][$framenumber]['copyright_id_start'] = (bool) $info['aac']['header']['raw']['copyright_start'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($info['aac']['header']['raw']['mpeg_layer'] != 0) {
|
||||||
|
$info['warning'][] = 'Layer error - expected "0", found "'.$info['aac']['header']['raw']['mpeg_layer'].'" instead';
|
||||||
|
}
|
||||||
|
if ($info['aac']['header']['sample_frequency'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt AAC file: sample_frequency == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['audio']['sample_rate'] = $info['aac']['header']['sample_frequency'];
|
||||||
|
$info['audio']['channels'] = $info['aac']['header']['channels'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$FrameLength = ($header2 & 0x0003FFE0) >> 5;
|
||||||
|
|
||||||
|
if (!isset($BitrateCache[$FrameLength])) {
|
||||||
|
$BitrateCache[$FrameLength] = ($info['aac']['header']['sample_frequency'] / 1024) * $FrameLength * 8;
|
||||||
|
}
|
||||||
|
getid3_lib::safe_inc($info['aac']['bitrate_distribution'][$BitrateCache[$FrameLength]], 1);
|
||||||
|
|
||||||
|
$info['aac'][$framenumber]['aac_frame_length'] = $FrameLength;
|
||||||
|
|
||||||
|
$info['aac'][$framenumber]['adts_buffer_fullness'] = (($header2 & 0x0000001F) << 6) & (($header3 & 0xFC) >> 2);
|
||||||
|
if ($info['aac'][$framenumber]['adts_buffer_fullness'] == 0x07FF) {
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
} else {
|
||||||
|
$info['audio']['bitrate_mode'] = 'cbr';
|
||||||
|
}
|
||||||
|
$info['aac'][$framenumber]['num_raw_data_blocks'] = (($header3 & 0x03) >> 0);
|
||||||
|
|
||||||
|
if ($info['aac']['header']['crc_present']) {
|
||||||
|
//$info['aac'][$framenumber]['crc'] = getid3_lib::BigEndian2Int(substr($substring, 7, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$ReturnExtendedInfo) {
|
||||||
|
unset($info['aac'][$framenumber]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
$rounded_precision = 5000;
|
||||||
|
$info['aac']['bitrate_distribution_rounded'] = array();
|
||||||
|
foreach ($info['aac']['bitrate_distribution'] as $bitrate => $count) {
|
||||||
|
$rounded_bitrate = round($bitrate / $rounded_precision) * $rounded_precision;
|
||||||
|
getid3_lib::safe_inc($info['aac']['bitrate_distribution_rounded'][$rounded_bitrate], $count);
|
||||||
|
}
|
||||||
|
ksort($info['aac']['bitrate_distribution_rounded']);
|
||||||
|
*/
|
||||||
|
|
||||||
|
$byteoffset += $FrameLength;
|
||||||
|
if ((++$framenumber < $MaxFramesToScan) && (($byteoffset + 10) < $info['avdataend'])) {
|
||||||
|
|
||||||
|
// keep scanning
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['aac']['frames'] = $framenumber;
|
||||||
|
$info['playtime_seconds'] = ($info['avdataend'] / $byteoffset) * (($framenumber * 1024) / $info['aac']['header']['sample_frequency']); // (1 / % of file scanned) * (samples / (samples/sec)) = seconds
|
||||||
|
if ($info['playtime_seconds'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt AAC file: playtime_seconds == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
ksort($info['aac']['bitrate_distribution']);
|
||||||
|
|
||||||
|
$info['audio']['encoder_options'] = $info['aac']['header_type'].' '.$info['aac']['header']['profile'];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// should never get here.
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AACsampleRateLookup($samplerateid) {
|
||||||
|
static $AACsampleRateLookup = array();
|
||||||
|
if (empty($AACsampleRateLookup)) {
|
||||||
|
$AACsampleRateLookup[0] = 96000;
|
||||||
|
$AACsampleRateLookup[1] = 88200;
|
||||||
|
$AACsampleRateLookup[2] = 64000;
|
||||||
|
$AACsampleRateLookup[3] = 48000;
|
||||||
|
$AACsampleRateLookup[4] = 44100;
|
||||||
|
$AACsampleRateLookup[5] = 32000;
|
||||||
|
$AACsampleRateLookup[6] = 24000;
|
||||||
|
$AACsampleRateLookup[7] = 22050;
|
||||||
|
$AACsampleRateLookup[8] = 16000;
|
||||||
|
$AACsampleRateLookup[9] = 12000;
|
||||||
|
$AACsampleRateLookup[10] = 11025;
|
||||||
|
$AACsampleRateLookup[11] = 8000;
|
||||||
|
$AACsampleRateLookup[12] = 0;
|
||||||
|
$AACsampleRateLookup[13] = 0;
|
||||||
|
$AACsampleRateLookup[14] = 0;
|
||||||
|
$AACsampleRateLookup[15] = 0;
|
||||||
|
}
|
||||||
|
return (isset($AACsampleRateLookup[$samplerateid]) ? $AACsampleRateLookup[$samplerateid] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AACprofileLookup($profileid, $mpegversion) {
|
||||||
|
static $AACprofileLookup = array();
|
||||||
|
if (empty($AACprofileLookup)) {
|
||||||
|
$AACprofileLookup[2][0] = 'Main profile';
|
||||||
|
$AACprofileLookup[2][1] = 'Low Complexity profile (LC)';
|
||||||
|
$AACprofileLookup[2][2] = 'Scalable Sample Rate profile (SSR)';
|
||||||
|
$AACprofileLookup[2][3] = '(reserved)';
|
||||||
|
$AACprofileLookup[4][0] = 'AAC_MAIN';
|
||||||
|
$AACprofileLookup[4][1] = 'AAC_LC';
|
||||||
|
$AACprofileLookup[4][2] = 'AAC_SSR';
|
||||||
|
$AACprofileLookup[4][3] = 'AAC_LTP';
|
||||||
|
}
|
||||||
|
return (isset($AACprofileLookup[$mpegversion][$profileid]) ? $AACprofileLookup[$mpegversion][$profileid] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AACchannelCountCalculate($program_configs) {
|
||||||
|
$channels = 0;
|
||||||
|
for ($i = 0; $i < $program_configs['num_front_channel_elements']; $i++) {
|
||||||
|
$channels++;
|
||||||
|
if ($program_configs['front_element_is_cpe'][$i]) {
|
||||||
|
// each front element is channel pair (CPE = Channel Pair Element)
|
||||||
|
$channels++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ($i = 0; $i < $program_configs['num_side_channel_elements']; $i++) {
|
||||||
|
$channels++;
|
||||||
|
if ($program_configs['side_element_is_cpe'][$i]) {
|
||||||
|
// each side element is channel pair (CPE = Channel Pair Element)
|
||||||
|
$channels++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ($i = 0; $i < $program_configs['num_back_channel_elements']; $i++) {
|
||||||
|
$channels++;
|
||||||
|
if ($program_configs['back_element_is_cpe'][$i]) {
|
||||||
|
// each back element is channel pair (CPE = Channel Pair Element)
|
||||||
|
$channels++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ($i = 0; $i < $program_configs['num_lfe_channel_elements']; $i++) {
|
||||||
|
$channels++;
|
||||||
|
}
|
||||||
|
return $channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
473
app/library/getid3/module.audio.ac3.php
Normal file
473
app/library/getid3/module.audio.ac3.php
Normal file
|
@ -0,0 +1,473 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.ac3.php //
|
||||||
|
// module for analyzing AC-3 (aka Dolby Digital) audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_ac3 extends getid3_handler
|
||||||
|
{
|
||||||
|
private $AC3header = '';
|
||||||
|
private $BSIoffset = 0;
|
||||||
|
|
||||||
|
|
||||||
|
public function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
///AH
|
||||||
|
$info['ac3']['raw']['bsi'] = array();
|
||||||
|
$thisfile_ac3 = &$info['ac3'];
|
||||||
|
$thisfile_ac3_raw = &$thisfile_ac3['raw'];
|
||||||
|
$thisfile_ac3_raw_bsi = &$thisfile_ac3_raw['bsi'];
|
||||||
|
|
||||||
|
|
||||||
|
// http://www.atsc.org/standards/a_52a.pdf
|
||||||
|
|
||||||
|
$info['fileformat'] = 'ac3';
|
||||||
|
|
||||||
|
// An AC-3 serial coded audio bit stream is made up of a sequence of synchronization frames
|
||||||
|
// Each synchronization frame contains 6 coded audio blocks (AB), each of which represent 256
|
||||||
|
// new audio samples per channel. A synchronization information (SI) header at the beginning
|
||||||
|
// of each frame contains information needed to acquire and maintain synchronization. A
|
||||||
|
// bit stream information (BSI) header follows SI, and contains parameters describing the coded
|
||||||
|
// audio service. The coded audio blocks may be followed by an auxiliary data (Aux) field. At the
|
||||||
|
// end of each frame is an error check field that includes a CRC word for error detection. An
|
||||||
|
// additional CRC word is located in the SI header, the use of which, by a decoder, is optional.
|
||||||
|
//
|
||||||
|
// syncinfo() | bsi() | AB0 | AB1 | AB2 | AB3 | AB4 | AB5 | Aux | CRC
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$this->AC3header['syncinfo'] = fread($this->getid3->fp, 5);
|
||||||
|
$thisfile_ac3_raw['synchinfo']['synchword'] = substr($this->AC3header['syncinfo'], 0, 2);
|
||||||
|
|
||||||
|
$magic = "\x0B\x77";
|
||||||
|
if ($thisfile_ac3_raw['synchinfo']['synchword'] != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_ac3_raw['synchinfo']['synchword']).'"';
|
||||||
|
unset($info['fileformat'], $info['ac3']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['audio']['dataformat'] = 'ac3';
|
||||||
|
$info['audio']['bitrate_mode'] = 'cbr';
|
||||||
|
$info['audio']['lossless'] = false;
|
||||||
|
|
||||||
|
// syncinfo() {
|
||||||
|
// syncword 16
|
||||||
|
// crc1 16
|
||||||
|
// fscod 2
|
||||||
|
// frmsizecod 6
|
||||||
|
// } /* end of syncinfo */
|
||||||
|
|
||||||
|
$thisfile_ac3_raw['synchinfo']['crc1'] = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], 2, 2));
|
||||||
|
$ac3_synchinfo_fscod_frmsizecod = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], 4, 1));
|
||||||
|
$thisfile_ac3_raw['synchinfo']['fscod'] = ($ac3_synchinfo_fscod_frmsizecod & 0xC0) >> 6;
|
||||||
|
$thisfile_ac3_raw['synchinfo']['frmsizecod'] = ($ac3_synchinfo_fscod_frmsizecod & 0x3F);
|
||||||
|
|
||||||
|
$thisfile_ac3['sample_rate'] = $this->AC3sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']);
|
||||||
|
if ($thisfile_ac3_raw['synchinfo']['fscod'] <= 3) {
|
||||||
|
$info['audio']['sample_rate'] = $thisfile_ac3['sample_rate'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_ac3['frame_length'] = $this->AC3frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']);
|
||||||
|
$thisfile_ac3['bitrate'] = $this->AC3bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']);
|
||||||
|
$info['audio']['bitrate'] = $thisfile_ac3['bitrate'];
|
||||||
|
|
||||||
|
$this->AC3header['bsi'] = getid3_lib::BigEndian2Bin(fread($this->getid3->fp, 15));
|
||||||
|
$ac3_bsi_offset = 0;
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5);
|
||||||
|
if ($thisfile_ac3_raw_bsi['bsid'] > 8) {
|
||||||
|
// Decoders which can decode version 8 will thus be able to decode version numbers less than 8.
|
||||||
|
// If this standard is extended by the addition of additional elements or features, a value of bsid greater than 8 will be used.
|
||||||
|
// Decoders built to this version of the standard will not be able to decode versions with bsid greater than 8.
|
||||||
|
$info['error'][] = 'Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 8';
|
||||||
|
unset($thisfile_ac3);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3);
|
||||||
|
$thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3);
|
||||||
|
|
||||||
|
$thisfile_ac3['service_type'] = $this->AC3serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']);
|
||||||
|
$ac3_coding_mode = $this->AC3audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']);
|
||||||
|
foreach($ac3_coding_mode as $key => $value) {
|
||||||
|
$thisfile_ac3[$key] = $value;
|
||||||
|
}
|
||||||
|
switch ($thisfile_ac3_raw_bsi['acmod']) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
$info['audio']['channelmode'] = 'mono';
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
$info['audio']['channelmode'] = 'stereo';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$info['audio']['channelmode'] = 'surround';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$info['audio']['channels'] = $thisfile_ac3['num_channels'];
|
||||||
|
|
||||||
|
if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) {
|
||||||
|
// If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream.
|
||||||
|
$thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2);
|
||||||
|
$thisfile_ac3['center_mix_level'] = $this->AC3centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) {
|
||||||
|
// If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream.
|
||||||
|
$thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2);
|
||||||
|
$thisfile_ac3['surround_mix_level'] = $this->AC3surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) {
|
||||||
|
// When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround.
|
||||||
|
$thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2);
|
||||||
|
$thisfile_ac3['dolby_surround_mode'] = $this->AC3dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['lfeon'] = (bool) $this->readHeaderBSI(1);
|
||||||
|
$thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['lfeon'];
|
||||||
|
if ($thisfile_ac3_raw_bsi['lfeon']) {
|
||||||
|
//$info['audio']['channels']++;
|
||||||
|
$info['audio']['channels'] .= '.1';
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_ac3['channels_enabled'] = $this->AC3channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['lfeon']);
|
||||||
|
|
||||||
|
// This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1–31.
|
||||||
|
// The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
|
||||||
|
$thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5);
|
||||||
|
$thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB';
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['compre_flag'] = (bool) $this->readHeaderBSI(1);
|
||||||
|
if ($thisfile_ac3_raw_bsi['compre_flag']) {
|
||||||
|
$thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8);
|
||||||
|
$thisfile_ac3['heavy_compression'] = $this->AC3heavyCompression($thisfile_ac3_raw_bsi['compr']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['langcode_flag'] = (bool) $this->readHeaderBSI(1);
|
||||||
|
if ($thisfile_ac3_raw_bsi['langcode_flag']) {
|
||||||
|
$thisfile_ac3_raw_bsi['langcod'] = $this->readHeaderBSI(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['audprodie'] = (bool) $this->readHeaderBSI(1);
|
||||||
|
if ($thisfile_ac3_raw_bsi['audprodie']) {
|
||||||
|
$thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5);
|
||||||
|
$thisfile_ac3_raw_bsi['roomtyp'] = $this->readHeaderBSI(2);
|
||||||
|
|
||||||
|
$thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB';
|
||||||
|
$thisfile_ac3['room_type'] = $this->AC3roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($thisfile_ac3_raw_bsi['acmod'] == 0x00) {
|
||||||
|
// If acmod is 0, then two completely independent program channels (dual mono)
|
||||||
|
// are encoded into the bit stream, and are referenced as Ch1, Ch2. In this case,
|
||||||
|
// a number of additional items are present in BSI or audblk to fully describe Ch2.
|
||||||
|
|
||||||
|
// This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1–31.
|
||||||
|
// The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
|
||||||
|
$thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5);
|
||||||
|
$thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB';
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['compre_flag2'] = (bool) $this->readHeaderBSI(1);
|
||||||
|
if ($thisfile_ac3_raw_bsi['compre_flag2']) {
|
||||||
|
$thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8);
|
||||||
|
$thisfile_ac3['heavy_compression2'] = $this->AC3heavyCompression($thisfile_ac3_raw_bsi['compr2']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['langcode_flag2'] = (bool) $this->readHeaderBSI(1);
|
||||||
|
if ($thisfile_ac3_raw_bsi['langcode_flag2']) {
|
||||||
|
$thisfile_ac3_raw_bsi['langcod2'] = $this->readHeaderBSI(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['audprodie2'] = (bool) $this->readHeaderBSI(1);
|
||||||
|
if ($thisfile_ac3_raw_bsi['audprodie2']) {
|
||||||
|
$thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5);
|
||||||
|
$thisfile_ac3_raw_bsi['roomtyp2'] = $this->readHeaderBSI(2);
|
||||||
|
|
||||||
|
$thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB';
|
||||||
|
$thisfile_ac3['room_type2'] = $this->AC3roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['copyright'] = (bool) $this->readHeaderBSI(1);
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['original'] = (bool) $this->readHeaderBSI(1);
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['timecode1_flag'] = (bool) $this->readHeaderBSI(1);
|
||||||
|
if ($thisfile_ac3_raw_bsi['timecode1_flag']) {
|
||||||
|
$thisfile_ac3_raw_bsi['timecode1'] = $this->readHeaderBSI(14);
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['timecode2_flag'] = (bool) $this->readHeaderBSI(1);
|
||||||
|
if ($thisfile_ac3_raw_bsi['timecode2_flag']) {
|
||||||
|
$thisfile_ac3_raw_bsi['timecode2'] = $this->readHeaderBSI(14);
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['addbsi_flag'] = (bool) $this->readHeaderBSI(1);
|
||||||
|
if ($thisfile_ac3_raw_bsi['addbsi_flag']) {
|
||||||
|
$thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6);
|
||||||
|
|
||||||
|
$this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin(fread($this->getid3->fp, $thisfile_ac3_raw_bsi['addbsi_length']));
|
||||||
|
|
||||||
|
$thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8);
|
||||||
|
$this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readHeaderBSI($length) {
|
||||||
|
$data = substr($this->AC3header['bsi'], $this->BSIoffset, $length);
|
||||||
|
$this->BSIoffset += $length;
|
||||||
|
|
||||||
|
return bindec($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AC3sampleRateCodeLookup($fscod) {
|
||||||
|
static $AC3sampleRateCodeLookup = array(
|
||||||
|
0 => 48000,
|
||||||
|
1 => 44100,
|
||||||
|
2 => 32000,
|
||||||
|
3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute.
|
||||||
|
);
|
||||||
|
return (isset($AC3sampleRateCodeLookup[$fscod]) ? $AC3sampleRateCodeLookup[$fscod] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AC3serviceTypeLookup($bsmod, $acmod) {
|
||||||
|
static $AC3serviceTypeLookup = array();
|
||||||
|
if (empty($AC3serviceTypeLookup)) {
|
||||||
|
for ($i = 0; $i <= 7; $i++) {
|
||||||
|
$AC3serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)';
|
||||||
|
$AC3serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)';
|
||||||
|
$AC3serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)';
|
||||||
|
$AC3serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)';
|
||||||
|
$AC3serviceTypeLookup[4][$i] = 'associated service: dialogue (D)';
|
||||||
|
$AC3serviceTypeLookup[5][$i] = 'associated service: commentary (C)';
|
||||||
|
$AC3serviceTypeLookup[6][$i] = 'associated service: emergency (E)';
|
||||||
|
}
|
||||||
|
|
||||||
|
$AC3serviceTypeLookup[7][1] = 'associated service: voice over (VO)';
|
||||||
|
for ($i = 2; $i <= 7; $i++) {
|
||||||
|
$AC3serviceTypeLookup[7][$i] = 'main audio service: karaoke';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (isset($AC3serviceTypeLookup[$bsmod][$acmod]) ? $AC3serviceTypeLookup[$bsmod][$acmod] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AC3audioCodingModeLookup($acmod) {
|
||||||
|
static $AC3audioCodingModeLookup = array();
|
||||||
|
if (empty($AC3audioCodingModeLookup)) {
|
||||||
|
// array(channel configuration, # channels (not incl LFE), channel order)
|
||||||
|
$AC3audioCodingModeLookup = array (
|
||||||
|
0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'),
|
||||||
|
1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'),
|
||||||
|
2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'),
|
||||||
|
3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'),
|
||||||
|
4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'),
|
||||||
|
5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'),
|
||||||
|
6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'),
|
||||||
|
7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (isset($AC3audioCodingModeLookup[$acmod]) ? $AC3audioCodingModeLookup[$acmod] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AC3centerMixLevelLookup($cmixlev) {
|
||||||
|
static $AC3centerMixLevelLookup;
|
||||||
|
if (empty($AC3centerMixLevelLookup)) {
|
||||||
|
$AC3centerMixLevelLookup = array(
|
||||||
|
0 => pow(2, -3.0 / 6), // 0.707 (–3.0 dB)
|
||||||
|
1 => pow(2, -4.5 / 6), // 0.595 (–4.5 dB)
|
||||||
|
2 => pow(2, -6.0 / 6), // 0.500 (–6.0 dB)
|
||||||
|
3 => 'reserved'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (isset($AC3centerMixLevelLookup[$cmixlev]) ? $AC3centerMixLevelLookup[$cmixlev] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AC3surroundMixLevelLookup($surmixlev) {
|
||||||
|
static $AC3surroundMixLevelLookup;
|
||||||
|
if (empty($AC3surroundMixLevelLookup)) {
|
||||||
|
$AC3surroundMixLevelLookup = array(
|
||||||
|
0 => pow(2, -3.0 / 6),
|
||||||
|
1 => pow(2, -6.0 / 6),
|
||||||
|
2 => 0,
|
||||||
|
3 => 'reserved'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (isset($AC3surroundMixLevelLookup[$surmixlev]) ? $AC3surroundMixLevelLookup[$surmixlev] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AC3dolbySurroundModeLookup($dsurmod) {
|
||||||
|
static $AC3dolbySurroundModeLookup = array(
|
||||||
|
0 => 'not indicated',
|
||||||
|
1 => 'Not Dolby Surround encoded',
|
||||||
|
2 => 'Dolby Surround encoded',
|
||||||
|
3 => 'reserved'
|
||||||
|
);
|
||||||
|
return (isset($AC3dolbySurroundModeLookup[$dsurmod]) ? $AC3dolbySurroundModeLookup[$dsurmod] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AC3channelsEnabledLookup($acmod, $lfeon) {
|
||||||
|
$AC3channelsEnabledLookup = array(
|
||||||
|
'ch1'=>(bool) ($acmod == 0),
|
||||||
|
'ch2'=>(bool) ($acmod == 0),
|
||||||
|
'left'=>(bool) ($acmod > 1),
|
||||||
|
'right'=>(bool) ($acmod > 1),
|
||||||
|
'center'=>(bool) ($acmod & 0x01),
|
||||||
|
'surround_mono'=>false,
|
||||||
|
'surround_left'=>false,
|
||||||
|
'surround_right'=>false,
|
||||||
|
'lfe'=>$lfeon);
|
||||||
|
switch ($acmod) {
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
$AC3channelsEnabledLookup['surround_mono'] = true;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
case 7:
|
||||||
|
$AC3channelsEnabledLookup['surround_left'] = true;
|
||||||
|
$AC3channelsEnabledLookup['surround_right'] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return $AC3channelsEnabledLookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AC3heavyCompression($compre) {
|
||||||
|
// The first four bits indicate gain changes in 6.02dB increments which can be
|
||||||
|
// implemented with an arithmetic shift operation. The following four bits
|
||||||
|
// indicate linear gain changes, and require a 5-bit multiply.
|
||||||
|
// We will represent the two 4-bit fields of compr as follows:
|
||||||
|
// X0 X1 X2 X3 . Y4 Y5 Y6 Y7
|
||||||
|
// The meaning of the X values is most simply described by considering X to represent a 4-bit
|
||||||
|
// signed integer with values from –8 to +7. The gain indicated by X is then (X + 1) * 6.02 dB. The
|
||||||
|
// following table shows this in detail.
|
||||||
|
|
||||||
|
// Meaning of 4 msb of compr
|
||||||
|
// 7 +48.16 dB
|
||||||
|
// 6 +42.14 dB
|
||||||
|
// 5 +36.12 dB
|
||||||
|
// 4 +30.10 dB
|
||||||
|
// 3 +24.08 dB
|
||||||
|
// 2 +18.06 dB
|
||||||
|
// 1 +12.04 dB
|
||||||
|
// 0 +6.02 dB
|
||||||
|
// -1 0 dB
|
||||||
|
// -2 –6.02 dB
|
||||||
|
// -3 –12.04 dB
|
||||||
|
// -4 –18.06 dB
|
||||||
|
// -5 –24.08 dB
|
||||||
|
// -6 –30.10 dB
|
||||||
|
// -7 –36.12 dB
|
||||||
|
// -8 –42.14 dB
|
||||||
|
|
||||||
|
$fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT);
|
||||||
|
if ($fourbit{0} == '1') {
|
||||||
|
$log_gain = -8 + bindec(substr($fourbit, 1));
|
||||||
|
} else {
|
||||||
|
$log_gain = bindec(substr($fourbit, 1));
|
||||||
|
}
|
||||||
|
$log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2);
|
||||||
|
|
||||||
|
// The value of Y is a linear representation of a gain change of up to –6 dB. Y is considered to
|
||||||
|
// be an unsigned fractional integer, with a leading value of 1, or: 0.1 Y4 Y5 Y6 Y7 (base 2). Y can
|
||||||
|
// represent values between 0.111112 (or 31/32) and 0.100002 (or 1/2). Thus, Y can represent gain
|
||||||
|
// changes from –0.28 dB to –6.02 dB.
|
||||||
|
|
||||||
|
$lin_gain = (16 + ($compre & 0x0F)) / 32;
|
||||||
|
|
||||||
|
// The combination of X and Y values allows compr to indicate gain changes from
|
||||||
|
// 48.16 – 0.28 = +47.89 dB, to
|
||||||
|
// –42.14 – 6.02 = –48.16 dB.
|
||||||
|
|
||||||
|
return $log_gain - $lin_gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AC3roomTypeLookup($roomtyp) {
|
||||||
|
static $AC3roomTypeLookup = array(
|
||||||
|
0 => 'not indicated',
|
||||||
|
1 => 'large room, X curve monitor',
|
||||||
|
2 => 'small room, flat monitor',
|
||||||
|
3 => 'reserved'
|
||||||
|
);
|
||||||
|
return (isset($AC3roomTypeLookup[$roomtyp]) ? $AC3roomTypeLookup[$roomtyp] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AC3frameSizeLookup($frmsizecod, $fscod) {
|
||||||
|
$padding = (bool) ($frmsizecod % 2);
|
||||||
|
$framesizeid = floor($frmsizecod / 2);
|
||||||
|
|
||||||
|
static $AC3frameSizeLookup = array();
|
||||||
|
if (empty($AC3frameSizeLookup)) {
|
||||||
|
$AC3frameSizeLookup = array (
|
||||||
|
0 => array(128, 138, 192),
|
||||||
|
1 => array(40, 160, 174, 240),
|
||||||
|
2 => array(48, 192, 208, 288),
|
||||||
|
3 => array(56, 224, 242, 336),
|
||||||
|
4 => array(64, 256, 278, 384),
|
||||||
|
5 => array(80, 320, 348, 480),
|
||||||
|
6 => array(96, 384, 416, 576),
|
||||||
|
7 => array(112, 448, 486, 672),
|
||||||
|
8 => array(128, 512, 556, 768),
|
||||||
|
9 => array(160, 640, 696, 960),
|
||||||
|
10 => array(192, 768, 834, 1152),
|
||||||
|
11 => array(224, 896, 974, 1344),
|
||||||
|
12 => array(256, 1024, 1114, 1536),
|
||||||
|
13 => array(320, 1280, 1392, 1920),
|
||||||
|
14 => array(384, 1536, 1670, 2304),
|
||||||
|
15 => array(448, 1792, 1950, 2688),
|
||||||
|
16 => array(512, 2048, 2228, 3072),
|
||||||
|
17 => array(576, 2304, 2506, 3456),
|
||||||
|
18 => array(640, 2560, 2786, 3840)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (($fscod == 1) && $padding) {
|
||||||
|
// frame lengths are padded by 1 word (16 bits) at 44100
|
||||||
|
$AC3frameSizeLookup[$frmsizecod] += 2;
|
||||||
|
}
|
||||||
|
return (isset($AC3frameSizeLookup[$framesizeid][$fscod]) ? $AC3frameSizeLookup[$framesizeid][$fscod] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function AC3bitrateLookup($frmsizecod) {
|
||||||
|
$framesizeid = floor($frmsizecod / 2);
|
||||||
|
|
||||||
|
static $AC3bitrateLookup = array(
|
||||||
|
0 => 32000,
|
||||||
|
1 => 40000,
|
||||||
|
2 => 48000,
|
||||||
|
3 => 56000,
|
||||||
|
4 => 64000,
|
||||||
|
5 => 80000,
|
||||||
|
6 => 96000,
|
||||||
|
7 => 112000,
|
||||||
|
8 => 128000,
|
||||||
|
9 => 160000,
|
||||||
|
10 => 192000,
|
||||||
|
11 => 224000,
|
||||||
|
12 => 256000,
|
||||||
|
13 => 320000,
|
||||||
|
14 => 384000,
|
||||||
|
15 => 448000,
|
||||||
|
16 => 512000,
|
||||||
|
17 => 576000,
|
||||||
|
18 => 640000
|
||||||
|
);
|
||||||
|
return (isset($AC3bitrateLookup[$framesizeid]) ? $AC3bitrateLookup[$framesizeid] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
165
app/library/getid3/module.audio.au.php
Normal file
165
app/library/getid3/module.audio.au.php
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.au.php //
|
||||||
|
// module for analyzing AU files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_au extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$AUheader = fread($this->getid3->fp, 8);
|
||||||
|
|
||||||
|
$magic = '.snd';
|
||||||
|
if (substr($AUheader, 0, 4) != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" (".snd") at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($AUheader, 0, 4)).'"';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$info['au'] = array();
|
||||||
|
$thisfile_au = &$info['au'];
|
||||||
|
|
||||||
|
$info['fileformat'] = 'au';
|
||||||
|
$info['audio']['dataformat'] = 'au';
|
||||||
|
$info['audio']['bitrate_mode'] = 'cbr';
|
||||||
|
$thisfile_au['encoding'] = 'ISO-8859-1';
|
||||||
|
|
||||||
|
$thisfile_au['header_length'] = getid3_lib::BigEndian2Int(substr($AUheader, 4, 4));
|
||||||
|
$AUheader .= fread($this->getid3->fp, $thisfile_au['header_length'] - 8);
|
||||||
|
$info['avdataoffset'] += $thisfile_au['header_length'];
|
||||||
|
|
||||||
|
$thisfile_au['data_size'] = getid3_lib::BigEndian2Int(substr($AUheader, 8, 4));
|
||||||
|
$thisfile_au['data_format_id'] = getid3_lib::BigEndian2Int(substr($AUheader, 12, 4));
|
||||||
|
$thisfile_au['sample_rate'] = getid3_lib::BigEndian2Int(substr($AUheader, 16, 4));
|
||||||
|
$thisfile_au['channels'] = getid3_lib::BigEndian2Int(substr($AUheader, 20, 4));
|
||||||
|
$thisfile_au['comments']['comment'][] = trim(substr($AUheader, 24));
|
||||||
|
|
||||||
|
$thisfile_au['data_format'] = $this->AUdataFormatNameLookup($thisfile_au['data_format_id']);
|
||||||
|
$thisfile_au['used_bits_per_sample'] = $this->AUdataFormatUsedBitsPerSampleLookup($thisfile_au['data_format_id']);
|
||||||
|
if ($thisfile_au['bits_per_sample'] = $this->AUdataFormatBitsPerSampleLookup($thisfile_au['data_format_id'])) {
|
||||||
|
$info['audio']['bits_per_sample'] = $thisfile_au['bits_per_sample'];
|
||||||
|
} else {
|
||||||
|
unset($thisfile_au['bits_per_sample']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['audio']['sample_rate'] = $thisfile_au['sample_rate'];
|
||||||
|
$info['audio']['channels'] = $thisfile_au['channels'];
|
||||||
|
|
||||||
|
if (($info['avdataoffset'] + $thisfile_au['data_size']) > $info['avdataend']) {
|
||||||
|
$info['warning'][] = 'Possible truncated file - expecting "'.$thisfile_au['data_size'].'" bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' bytes"';
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = $thisfile_au['data_size'] / ($thisfile_au['sample_rate'] * $thisfile_au['channels'] * ($thisfile_au['used_bits_per_sample'] / 8));
|
||||||
|
$info['audio']['bitrate'] = ($thisfile_au['data_size'] * 8) / $info['playtime_seconds'];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AUdataFormatNameLookup($id) {
|
||||||
|
static $AUdataFormatNameLookup = array(
|
||||||
|
0 => 'unspecified format',
|
||||||
|
1 => '8-bit mu-law',
|
||||||
|
2 => '8-bit linear',
|
||||||
|
3 => '16-bit linear',
|
||||||
|
4 => '24-bit linear',
|
||||||
|
5 => '32-bit linear',
|
||||||
|
6 => 'floating-point',
|
||||||
|
7 => 'double-precision float',
|
||||||
|
8 => 'fragmented sampled data',
|
||||||
|
9 => 'SUN_FORMAT_NESTED',
|
||||||
|
10 => 'DSP program',
|
||||||
|
11 => '8-bit fixed-point',
|
||||||
|
12 => '16-bit fixed-point',
|
||||||
|
13 => '24-bit fixed-point',
|
||||||
|
14 => '32-bit fixed-point',
|
||||||
|
|
||||||
|
16 => 'non-audio display data',
|
||||||
|
17 => 'SND_FORMAT_MULAW_SQUELCH',
|
||||||
|
18 => '16-bit linear with emphasis',
|
||||||
|
19 => '16-bit linear with compression',
|
||||||
|
20 => '16-bit linear with emphasis + compression',
|
||||||
|
21 => 'Music Kit DSP commands',
|
||||||
|
22 => 'SND_FORMAT_DSP_COMMANDS_SAMPLES',
|
||||||
|
23 => 'CCITT g.721 4-bit ADPCM',
|
||||||
|
24 => 'CCITT g.722 ADPCM',
|
||||||
|
25 => 'CCITT g.723 3-bit ADPCM',
|
||||||
|
26 => 'CCITT g.723 5-bit ADPCM',
|
||||||
|
27 => 'A-Law 8-bit'
|
||||||
|
);
|
||||||
|
return (isset($AUdataFormatNameLookup[$id]) ? $AUdataFormatNameLookup[$id] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AUdataFormatBitsPerSampleLookup($id) {
|
||||||
|
static $AUdataFormatBitsPerSampleLookup = array(
|
||||||
|
1 => 8,
|
||||||
|
2 => 8,
|
||||||
|
3 => 16,
|
||||||
|
4 => 24,
|
||||||
|
5 => 32,
|
||||||
|
6 => 32,
|
||||||
|
7 => 64,
|
||||||
|
|
||||||
|
11 => 8,
|
||||||
|
12 => 16,
|
||||||
|
13 => 24,
|
||||||
|
14 => 32,
|
||||||
|
|
||||||
|
18 => 16,
|
||||||
|
19 => 16,
|
||||||
|
20 => 16,
|
||||||
|
|
||||||
|
23 => 16,
|
||||||
|
|
||||||
|
25 => 16,
|
||||||
|
26 => 16,
|
||||||
|
27 => 8
|
||||||
|
);
|
||||||
|
return (isset($AUdataFormatBitsPerSampleLookup[$id]) ? $AUdataFormatBitsPerSampleLookup[$id] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AUdataFormatUsedBitsPerSampleLookup($id) {
|
||||||
|
static $AUdataFormatUsedBitsPerSampleLookup = array(
|
||||||
|
1 => 8,
|
||||||
|
2 => 8,
|
||||||
|
3 => 16,
|
||||||
|
4 => 24,
|
||||||
|
5 => 32,
|
||||||
|
6 => 32,
|
||||||
|
7 => 64,
|
||||||
|
|
||||||
|
11 => 8,
|
||||||
|
12 => 16,
|
||||||
|
13 => 24,
|
||||||
|
14 => 32,
|
||||||
|
|
||||||
|
18 => 16,
|
||||||
|
19 => 16,
|
||||||
|
20 => 16,
|
||||||
|
|
||||||
|
23 => 4,
|
||||||
|
|
||||||
|
25 => 3,
|
||||||
|
26 => 5,
|
||||||
|
27 => 8,
|
||||||
|
);
|
||||||
|
return (isset($AUdataFormatUsedBitsPerSampleLookup[$id]) ? $AUdataFormatUsedBitsPerSampleLookup[$id] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
127
app/library/getid3/module.audio.avr.php
Normal file
127
app/library/getid3/module.audio.avr.php
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.avr.php //
|
||||||
|
// module for analyzing AVR Audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_avr extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
// http://cui.unige.ch/OSG/info/AudioFormats/ap11.html
|
||||||
|
// http://www.btinternet.com/~AnthonyJ/Atari/programming/avr_format.html
|
||||||
|
// offset type length name comments
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// 0 char 4 ID format ID == "2BIT"
|
||||||
|
// 4 char 8 name sample name (unused space filled with 0)
|
||||||
|
// 12 short 1 mono/stereo 0=mono, -1 (0xFFFF)=stereo
|
||||||
|
// With stereo, samples are alternated,
|
||||||
|
// the first voice is the left :
|
||||||
|
// (LRLRLRLRLRLRLRLRLR...)
|
||||||
|
// 14 short 1 resolution 8, 12 or 16 (bits)
|
||||||
|
// 16 short 1 signed or not 0=unsigned, -1 (0xFFFF)=signed
|
||||||
|
// 18 short 1 loop or not 0=no loop, -1 (0xFFFF)=loop on
|
||||||
|
// 20 short 1 MIDI note 0xFFnn, where 0 <= nn <= 127
|
||||||
|
// 0xFFFF means "no MIDI note defined"
|
||||||
|
// 22 byte 1 Replay speed Frequence in the Replay software
|
||||||
|
// 0=5.485 Khz, 1=8.084 Khz, 2=10.971 Khz,
|
||||||
|
// 3=16.168 Khz, 4=21.942 Khz, 5=32.336 Khz
|
||||||
|
// 6=43.885 Khz, 7=47.261 Khz
|
||||||
|
// -1 (0xFF)=no defined Frequence
|
||||||
|
// 23 byte 3 sample rate in Hertz
|
||||||
|
// 26 long 1 size in bytes (2 * bytes in stereo)
|
||||||
|
// 30 long 1 loop begin 0 for no loop
|
||||||
|
// 34 long 1 loop size equal to 'size' for no loop
|
||||||
|
// 38 short 2 Reserved, MIDI keyboard split */
|
||||||
|
// 40 short 2 Reserved, sample compression */
|
||||||
|
// 42 short 2 Reserved */
|
||||||
|
// 44 char 20; Additional filename space, used if (name[7] != 0)
|
||||||
|
// 64 byte 64 user data
|
||||||
|
// 128 bytes ? sample data (12 bits samples are coded on 16 bits:
|
||||||
|
// 0000 xxxx xxxx xxxx)
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Note that all values are in motorola (big-endian) format, and that long is
|
||||||
|
// assumed to be 4 bytes, and short 2 bytes.
|
||||||
|
// When reading the samples, you should handle both signed and unsigned data,
|
||||||
|
// and be prepared to convert 16->8 bit, or mono->stereo if needed. To convert
|
||||||
|
// 8-bit data between signed/unsigned just add 127 to the sample values.
|
||||||
|
// Simularly for 16-bit data you should add 32769
|
||||||
|
|
||||||
|
$info['fileformat'] = 'avr';
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$AVRheader = fread($this->getid3->fp, 128);
|
||||||
|
|
||||||
|
$info['avr']['raw']['magic'] = substr($AVRheader, 0, 4);
|
||||||
|
$magic = '2BIT';
|
||||||
|
if ($info['avr']['raw']['magic'] != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['avr']['raw']['magic']).'"';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['avr']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['avdataoffset'] += 128;
|
||||||
|
|
||||||
|
$info['avr']['sample_name'] = rtrim(substr($AVRheader, 4, 8));
|
||||||
|
$info['avr']['raw']['mono'] = getid3_lib::BigEndian2Int(substr($AVRheader, 12, 2));
|
||||||
|
$info['avr']['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($AVRheader, 14, 2));
|
||||||
|
$info['avr']['raw']['signed'] = getid3_lib::BigEndian2Int(substr($AVRheader, 16, 2));
|
||||||
|
$info['avr']['raw']['loop'] = getid3_lib::BigEndian2Int(substr($AVRheader, 18, 2));
|
||||||
|
$info['avr']['raw']['midi'] = getid3_lib::BigEndian2Int(substr($AVRheader, 20, 2));
|
||||||
|
$info['avr']['raw']['replay_freq'] = getid3_lib::BigEndian2Int(substr($AVRheader, 22, 1));
|
||||||
|
$info['avr']['sample_rate'] = getid3_lib::BigEndian2Int(substr($AVRheader, 23, 3));
|
||||||
|
$info['avr']['sample_length'] = getid3_lib::BigEndian2Int(substr($AVRheader, 26, 4));
|
||||||
|
$info['avr']['loop_start'] = getid3_lib::BigEndian2Int(substr($AVRheader, 30, 4));
|
||||||
|
$info['avr']['loop_end'] = getid3_lib::BigEndian2Int(substr($AVRheader, 34, 4));
|
||||||
|
$info['avr']['midi_split'] = getid3_lib::BigEndian2Int(substr($AVRheader, 38, 2));
|
||||||
|
$info['avr']['sample_compression'] = getid3_lib::BigEndian2Int(substr($AVRheader, 40, 2));
|
||||||
|
$info['avr']['reserved'] = getid3_lib::BigEndian2Int(substr($AVRheader, 42, 2));
|
||||||
|
$info['avr']['sample_name_extra'] = rtrim(substr($AVRheader, 44, 20));
|
||||||
|
$info['avr']['comment'] = rtrim(substr($AVRheader, 64, 64));
|
||||||
|
|
||||||
|
$info['avr']['flags']['stereo'] = (($info['avr']['raw']['mono'] == 0) ? false : true);
|
||||||
|
$info['avr']['flags']['signed'] = (($info['avr']['raw']['signed'] == 0) ? false : true);
|
||||||
|
$info['avr']['flags']['loop'] = (($info['avr']['raw']['loop'] == 0) ? false : true);
|
||||||
|
|
||||||
|
$info['avr']['midi_notes'] = array();
|
||||||
|
if (($info['avr']['raw']['midi'] & 0xFF00) != 0xFF00) {
|
||||||
|
$info['avr']['midi_notes'][] = ($info['avr']['raw']['midi'] & 0xFF00) >> 8;
|
||||||
|
}
|
||||||
|
if (($info['avr']['raw']['midi'] & 0x00FF) != 0x00FF) {
|
||||||
|
$info['avr']['midi_notes'][] = ($info['avr']['raw']['midi'] & 0x00FF);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($info['avdataend'] - $info['avdataoffset']) != ($info['avr']['sample_length'] * (($info['avr']['bits_per_sample'] == 8) ? 1 : 2))) {
|
||||||
|
$info['warning'][] = 'Probable truncated file: expecting '.($info['avr']['sample_length'] * (($info['avr']['bits_per_sample'] == 8) ? 1 : 2)).' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['audio']['dataformat'] = 'avr';
|
||||||
|
$info['audio']['lossless'] = true;
|
||||||
|
$info['audio']['bitrate_mode'] = 'cbr';
|
||||||
|
$info['audio']['bits_per_sample'] = $info['avr']['bits_per_sample'];
|
||||||
|
$info['audio']['sample_rate'] = $info['avr']['sample_rate'];
|
||||||
|
$info['audio']['channels'] = ($info['avr']['flags']['stereo'] ? 2 : 1);
|
||||||
|
$info['playtime_seconds'] = ($info['avr']['sample_length'] / $info['audio']['channels']) / $info['avr']['sample_rate'];
|
||||||
|
$info['audio']['bitrate'] = ($info['avr']['sample_length'] * (($info['avr']['bits_per_sample'] == 8) ? 8 : 16)) / $info['playtime_seconds'];
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
230
app/library/getid3/module.audio.bonk.php
Normal file
230
app/library/getid3/module.audio.bonk.php
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.la.php //
|
||||||
|
// module for analyzing BONK audio files //
|
||||||
|
// dependencies: module.tag.id3v2.php (optional) //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_bonk extends getid3_handler
|
||||||
|
{
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$info['bonk'] = array();
|
||||||
|
$thisfile_bonk = &$info['bonk'];
|
||||||
|
|
||||||
|
$thisfile_bonk['dataoffset'] = $info['avdataoffset'];
|
||||||
|
$thisfile_bonk['dataend'] = $info['avdataend'];
|
||||||
|
|
||||||
|
if (!getid3_lib::intValueSupported($thisfile_bonk['dataend'])) {
|
||||||
|
|
||||||
|
$info['warning'][] = 'Unable to parse BONK file from end (v0.6+ preferred method) because PHP filesystem functions only support up to '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// scan-from-end method, for v0.6 and higher
|
||||||
|
fseek($this->getid3->fp, $thisfile_bonk['dataend'] - 8, SEEK_SET);
|
||||||
|
$PossibleBonkTag = fread($this->getid3->fp, 8);
|
||||||
|
while ($this->BonkIsValidTagName(substr($PossibleBonkTag, 4, 4), true)) {
|
||||||
|
$BonkTagSize = getid3_lib::LittleEndian2Int(substr($PossibleBonkTag, 0, 4));
|
||||||
|
fseek($this->getid3->fp, 0 - $BonkTagSize, SEEK_CUR);
|
||||||
|
$BonkTagOffset = ftell($this->getid3->fp);
|
||||||
|
$TagHeaderTest = fread($this->getid3->fp, 5);
|
||||||
|
if (($TagHeaderTest{0} != "\x00") || (substr($PossibleBonkTag, 4, 4) != strtolower(substr($PossibleBonkTag, 4, 4)))) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes("\x00".strtoupper(substr($PossibleBonkTag, 4, 4))).'" at offset '.$BonkTagOffset.', found "'.getid3_lib::PrintHexBytes($TagHeaderTest).'"';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$BonkTagName = substr($TagHeaderTest, 1, 4);
|
||||||
|
|
||||||
|
$thisfile_bonk[$BonkTagName]['size'] = $BonkTagSize;
|
||||||
|
$thisfile_bonk[$BonkTagName]['offset'] = $BonkTagOffset;
|
||||||
|
$this->HandleBonkTags($BonkTagName);
|
||||||
|
$NextTagEndOffset = $BonkTagOffset - 8;
|
||||||
|
if ($NextTagEndOffset < $thisfile_bonk['dataoffset']) {
|
||||||
|
if (empty($info['audio']['encoder'])) {
|
||||||
|
$info['audio']['encoder'] = 'Extended BONK v0.9+';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
fseek($this->getid3->fp, $NextTagEndOffset, SEEK_SET);
|
||||||
|
$PossibleBonkTag = fread($this->getid3->fp, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// seek-from-beginning method for v0.4 and v0.5
|
||||||
|
if (empty($thisfile_bonk['BONK'])) {
|
||||||
|
fseek($this->getid3->fp, $thisfile_bonk['dataoffset'], SEEK_SET);
|
||||||
|
do {
|
||||||
|
$TagHeaderTest = fread($this->getid3->fp, 5);
|
||||||
|
switch ($TagHeaderTest) {
|
||||||
|
case "\x00".'BONK':
|
||||||
|
if (empty($info['audio']['encoder'])) {
|
||||||
|
$info['audio']['encoder'] = 'BONK v0.4';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "\x00".'INFO':
|
||||||
|
$info['audio']['encoder'] = 'Extended BONK v0.5';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
$BonkTagName = substr($TagHeaderTest, 1, 4);
|
||||||
|
$thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset'];
|
||||||
|
$thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset'];
|
||||||
|
$this->HandleBonkTags($BonkTagName);
|
||||||
|
|
||||||
|
} while (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse META block for v0.6 - v0.8
|
||||||
|
if (empty($thisfile_bonk['INFO']) && isset($thisfile_bonk['META']['tags']['info'])) {
|
||||||
|
fseek($this->getid3->fp, $thisfile_bonk['META']['tags']['info'], SEEK_SET);
|
||||||
|
$TagHeaderTest = fread($this->getid3->fp, 5);
|
||||||
|
if ($TagHeaderTest == "\x00".'INFO') {
|
||||||
|
$info['audio']['encoder'] = 'Extended BONK v0.6 - v0.8';
|
||||||
|
|
||||||
|
$BonkTagName = substr($TagHeaderTest, 1, 4);
|
||||||
|
$thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset'];
|
||||||
|
$thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset'];
|
||||||
|
$this->HandleBonkTags($BonkTagName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($info['audio']['encoder'])) {
|
||||||
|
$info['audio']['encoder'] = 'Extended BONK v0.9+';
|
||||||
|
}
|
||||||
|
if (empty($thisfile_bonk['BONK'])) {
|
||||||
|
unset($info['bonk']);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function HandleBonkTags($BonkTagName) {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
switch ($BonkTagName) {
|
||||||
|
case 'BONK':
|
||||||
|
// shortcut
|
||||||
|
$thisfile_bonk_BONK = &$info['bonk']['BONK'];
|
||||||
|
|
||||||
|
$BonkData = "\x00".'BONK'.fread($this->getid3->fp, 17);
|
||||||
|
$thisfile_bonk_BONK['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1));
|
||||||
|
$thisfile_bonk_BONK['number_samples'] = getid3_lib::LittleEndian2Int(substr($BonkData, 6, 4));
|
||||||
|
$thisfile_bonk_BONK['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BonkData, 10, 4));
|
||||||
|
|
||||||
|
$thisfile_bonk_BONK['channels'] = getid3_lib::LittleEndian2Int(substr($BonkData, 14, 1));
|
||||||
|
$thisfile_bonk_BONK['lossless'] = (bool) getid3_lib::LittleEndian2Int(substr($BonkData, 15, 1));
|
||||||
|
$thisfile_bonk_BONK['joint_stereo'] = (bool) getid3_lib::LittleEndian2Int(substr($BonkData, 16, 1));
|
||||||
|
$thisfile_bonk_BONK['number_taps'] = getid3_lib::LittleEndian2Int(substr($BonkData, 17, 2));
|
||||||
|
$thisfile_bonk_BONK['downsampling_ratio'] = getid3_lib::LittleEndian2Int(substr($BonkData, 19, 1));
|
||||||
|
$thisfile_bonk_BONK['samples_per_packet'] = getid3_lib::LittleEndian2Int(substr($BonkData, 20, 2));
|
||||||
|
|
||||||
|
$info['avdataoffset'] = $thisfile_bonk_BONK['offset'] + 5 + 17;
|
||||||
|
$info['avdataend'] = $thisfile_bonk_BONK['offset'] + $thisfile_bonk_BONK['size'];
|
||||||
|
|
||||||
|
$info['fileformat'] = 'bonk';
|
||||||
|
$info['audio']['dataformat'] = 'bonk';
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr'; // assumed
|
||||||
|
$info['audio']['channels'] = $thisfile_bonk_BONK['channels'];
|
||||||
|
$info['audio']['sample_rate'] = $thisfile_bonk_BONK['sample_rate'];
|
||||||
|
$info['audio']['channelmode'] = ($thisfile_bonk_BONK['joint_stereo'] ? 'joint stereo' : 'stereo');
|
||||||
|
$info['audio']['lossless'] = $thisfile_bonk_BONK['lossless'];
|
||||||
|
$info['audio']['codec'] = 'bonk';
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = $thisfile_bonk_BONK['number_samples'] / ($thisfile_bonk_BONK['sample_rate'] * $thisfile_bonk_BONK['channels']);
|
||||||
|
if ($info['playtime_seconds'] > 0) {
|
||||||
|
$info['audio']['bitrate'] = (($info['bonk']['dataend'] - $info['bonk']['dataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'INFO':
|
||||||
|
// shortcut
|
||||||
|
$thisfile_bonk_INFO = &$info['bonk']['INFO'];
|
||||||
|
|
||||||
|
$thisfile_bonk_INFO['version'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1));
|
||||||
|
$thisfile_bonk_INFO['entries_count'] = 0;
|
||||||
|
$NextInfoDataPair = fread($this->getid3->fp, 5);
|
||||||
|
if (!$this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) {
|
||||||
|
while (!feof($this->getid3->fp)) {
|
||||||
|
//$CurrentSeekInfo['offset'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 0, 4));
|
||||||
|
//$CurrentSeekInfo['nextbit'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 4, 1));
|
||||||
|
//$thisfile_bonk_INFO[] = $CurrentSeekInfo;
|
||||||
|
|
||||||
|
$NextInfoDataPair = fread($this->getid3->fp, 5);
|
||||||
|
if ($this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) {
|
||||||
|
fseek($this->getid3->fp, -5, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$thisfile_bonk_INFO['entries_count']++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'META':
|
||||||
|
$BonkData = "\x00".'META'.fread($this->getid3->fp, $info['bonk']['META']['size'] - 5);
|
||||||
|
$info['bonk']['META']['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1));
|
||||||
|
|
||||||
|
$MetaTagEntries = floor(((strlen($BonkData) - 8) - 6) / 8); // BonkData - xxxxmeta - ØMETA
|
||||||
|
$offset = 6;
|
||||||
|
for ($i = 0; $i < $MetaTagEntries; $i++) {
|
||||||
|
$MetaEntryTagName = substr($BonkData, $offset, 4);
|
||||||
|
$offset += 4;
|
||||||
|
$MetaEntryTagOffset = getid3_lib::LittleEndian2Int(substr($BonkData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$info['bonk']['META']['tags'][$MetaEntryTagName] = $MetaEntryTagOffset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ' ID3':
|
||||||
|
$info['audio']['encoder'] = 'Extended BONK v0.9+';
|
||||||
|
|
||||||
|
// ID3v2 checking is optional
|
||||||
|
if (class_exists('getid3_id3v2')) {
|
||||||
|
$getid3_temp = new getID3();
|
||||||
|
$getid3_temp->openfile($this->getid3->filename);
|
||||||
|
$getid3_id3v2 = new getid3_id3v2($getid3_temp);
|
||||||
|
$getid3_id3v2->StartingOffset = $info['bonk'][' ID3']['offset'] + 2;
|
||||||
|
$info['bonk'][' ID3']['valid'] = $getid3_id3v2->Analyze();
|
||||||
|
if ($info['bonk'][' ID3']['valid']) {
|
||||||
|
$info['id3v2'] = $getid3_temp->info['id3v2'];
|
||||||
|
}
|
||||||
|
unset($getid3_temp, $getid3_id3v2);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['warning'][] = 'Unexpected Bonk tag "'.$BonkTagName.'" at offset '.$info['bonk'][$BonkTagName]['offset'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static function BonkIsValidTagName($PossibleBonkTag, $ignorecase=false) {
|
||||||
|
static $BonkIsValidTagName = array('BONK', 'INFO', ' ID3', 'META');
|
||||||
|
foreach ($BonkIsValidTagName as $validtagname) {
|
||||||
|
if ($validtagname == $PossibleBonkTag) {
|
||||||
|
return true;
|
||||||
|
} elseif ($ignorecase && (strtolower($validtagname) == strtolower($PossibleBonkTag))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
75
app/library/getid3/module.audio.dss.php
Normal file
75
app/library/getid3/module.audio.dss.php
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.dss.php //
|
||||||
|
// module for analyzing Digital Speech Standard (DSS) files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_dss extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$DSSheader = fread($this->getid3->fp, 1256);
|
||||||
|
|
||||||
|
if (!preg_match('#^(\x02|\x03)dss#', $DSSheader)) {
|
||||||
|
$info['error'][] = 'Expecting "[02-03] 64 73 73" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSSheader, 0, 4)).'"';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// some structure information taken from http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$info['dss'] = array();
|
||||||
|
$thisfile_dss = &$info['dss'];
|
||||||
|
|
||||||
|
$info['fileformat'] = 'dss';
|
||||||
|
$info['audio']['dataformat'] = 'dss';
|
||||||
|
$info['audio']['bitrate_mode'] = 'cbr';
|
||||||
|
//$thisfile_dss['encoding'] = 'ISO-8859-1';
|
||||||
|
|
||||||
|
$thisfile_dss['version'] = ord(substr($DSSheader, 0, 1));
|
||||||
|
$thisfile_dss['date_create'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 38, 12));
|
||||||
|
$thisfile_dss['date_complete'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 50, 12));
|
||||||
|
//$thisfile_dss['length'] = intval(substr($DSSheader, 62, 6)); // I thought time was in seconds, it's actually HHMMSS
|
||||||
|
$thisfile_dss['length'] = intval((substr($DSSheader, 62, 2) * 3600) + (substr($DSSheader, 64, 2) * 60) + substr($DSSheader, 66, 2));
|
||||||
|
$thisfile_dss['priority'] = ord(substr($DSSheader, 793, 1));
|
||||||
|
$thisfile_dss['comments'] = trim(substr($DSSheader, 798, 100));
|
||||||
|
|
||||||
|
|
||||||
|
//$info['audio']['bits_per_sample'] = ?;
|
||||||
|
//$info['audio']['sample_rate'] = ?;
|
||||||
|
$info['audio']['channels'] = 1;
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = $thisfile_dss['length'];
|
||||||
|
$info['audio']['bitrate'] = ($info['filesize'] * 8) / $info['playtime_seconds'];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DSSdateStringToUnixDate($datestring) {
|
||||||
|
$y = substr($datestring, 0, 2);
|
||||||
|
$m = substr($datestring, 2, 2);
|
||||||
|
$d = substr($datestring, 4, 2);
|
||||||
|
$h = substr($datestring, 6, 2);
|
||||||
|
$i = substr($datestring, 8, 2);
|
||||||
|
$s = substr($datestring, 10, 2);
|
||||||
|
$y += (($y < 95) ? 2000 : 1900);
|
||||||
|
return mktime($h, $i, $s, $m, $d, $y);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
246
app/library/getid3/module.audio.dts.php
Normal file
246
app/library/getid3/module.audio.dts.php
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.dts.php //
|
||||||
|
// module for analyzing DTS Audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_dts extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
public function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
// Specs taken from "DTS Coherent Acoustics;Core and Extensions, ETSI TS 102 114 V1.2.1 (2002-12)"
|
||||||
|
// (http://pda.etsi.org/pda/queryform.asp)
|
||||||
|
// With thanks to Gambit <macteam@users.sourceforge.net> http://mac.sourceforge.net/atl/
|
||||||
|
|
||||||
|
$info['fileformat'] = 'dts';
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$DTSheader = fread($this->getid3->fp, 16);
|
||||||
|
$info['dts']['raw']['magic'] = substr($DTSheader, 0, 4);
|
||||||
|
|
||||||
|
$magic = "\x7F\xFE\x80\x01";
|
||||||
|
if ($info['dts']['raw']['magic'] != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['dts']['raw']['magic']).'"';
|
||||||
|
unset($info['fileformat'], $info['dts']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fhBS = getid3_lib::BigEndian2Bin(substr($DTSheader, 4, 12));
|
||||||
|
$bsOffset = 0;
|
||||||
|
$info['dts']['raw']['frame_type'] = $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['raw']['deficit_samples'] = $this->readBinData($fhBS, $bsOffset, 5);
|
||||||
|
$info['dts']['flags']['crc_present'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['raw']['pcm_sample_blocks'] = $this->readBinData($fhBS, $bsOffset, 7);
|
||||||
|
$info['dts']['raw']['frame_byte_size'] = $this->readBinData($fhBS, $bsOffset, 14);
|
||||||
|
$info['dts']['raw']['channel_arrangement'] = $this->readBinData($fhBS, $bsOffset, 6);
|
||||||
|
$info['dts']['raw']['sample_frequency'] = $this->readBinData($fhBS, $bsOffset, 4);
|
||||||
|
$info['dts']['raw']['bitrate'] = $this->readBinData($fhBS, $bsOffset, 5);
|
||||||
|
$info['dts']['flags']['embedded_downmix'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['flags']['dynamicrange'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['flags']['timestamp'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['flags']['auxdata'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['flags']['hdcd'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['raw']['extension_audio'] = $this->readBinData($fhBS, $bsOffset, 3);
|
||||||
|
$info['dts']['flags']['extended_coding'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['raw']['lfe_effects'] = $this->readBinData($fhBS, $bsOffset, 2);
|
||||||
|
$info['dts']['flags']['predictor_history'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
if ($info['dts']['flags']['crc_present']) {
|
||||||
|
$info['dts']['raw']['crc16'] = $this->readBinData($fhBS, $bsOffset, 16);
|
||||||
|
}
|
||||||
|
$info['dts']['flags']['mri_perfect_reconst'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['raw']['encoder_soft_version'] = $this->readBinData($fhBS, $bsOffset, 4);
|
||||||
|
$info['dts']['raw']['copy_history'] = $this->readBinData($fhBS, $bsOffset, 2);
|
||||||
|
$info['dts']['raw']['bits_per_sample'] = $this->readBinData($fhBS, $bsOffset, 2);
|
||||||
|
$info['dts']['flags']['surround_es'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['flags']['front_sum_diff'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['flags']['surround_sum_diff'] = (bool) $this->readBinData($fhBS, $bsOffset, 1);
|
||||||
|
$info['dts']['raw']['dialog_normalization'] = $this->readBinData($fhBS, $bsOffset, 4);
|
||||||
|
|
||||||
|
|
||||||
|
$info['dts']['bitrate'] = self::DTSbitrateLookup($info['dts']['raw']['bitrate']);
|
||||||
|
$info['dts']['bits_per_sample'] = self::DTSbitPerSampleLookup($info['dts']['raw']['bits_per_sample']);
|
||||||
|
$info['dts']['sample_rate'] = self::DTSsampleRateLookup($info['dts']['raw']['sample_frequency']);
|
||||||
|
$info['dts']['dialog_normalization'] = self::DTSdialogNormalization($info['dts']['raw']['dialog_normalization'], $info['dts']['raw']['encoder_soft_version']);
|
||||||
|
$info['dts']['flags']['lossless'] = (($info['dts']['raw']['bitrate'] == 31) ? true : false);
|
||||||
|
$info['dts']['bitrate_mode'] = (($info['dts']['raw']['bitrate'] == 30) ? 'vbr' : 'cbr');
|
||||||
|
$info['dts']['channels'] = self::DTSnumChannelsLookup($info['dts']['raw']['channel_arrangement']);
|
||||||
|
$info['dts']['channel_arrangement'] = self::DTSchannelArrangementLookup($info['dts']['raw']['channel_arrangement']);
|
||||||
|
|
||||||
|
$info['audio']['dataformat'] = 'dts';
|
||||||
|
$info['audio']['lossless'] = $info['dts']['flags']['lossless'];
|
||||||
|
$info['audio']['bitrate_mode'] = $info['dts']['bitrate_mode'];
|
||||||
|
$info['audio']['bits_per_sample'] = $info['dts']['bits_per_sample'];
|
||||||
|
$info['audio']['sample_rate'] = $info['dts']['sample_rate'];
|
||||||
|
$info['audio']['channels'] = $info['dts']['channels'];
|
||||||
|
$info['audio']['bitrate'] = $info['dts']['bitrate'];
|
||||||
|
if (isset($info['avdataend'])) {
|
||||||
|
$info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($info['dts']['bitrate'] / 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readBinData($bin, &$offset, $length) {
|
||||||
|
$data = substr($bin, $offset, $length);
|
||||||
|
$offset += $length;
|
||||||
|
return bindec($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function DTSbitrateLookup($index) {
|
||||||
|
$DTSbitrateLookup = array(
|
||||||
|
0 => 32000,
|
||||||
|
1 => 56000,
|
||||||
|
2 => 64000,
|
||||||
|
3 => 96000,
|
||||||
|
4 => 112000,
|
||||||
|
5 => 128000,
|
||||||
|
6 => 192000,
|
||||||
|
7 => 224000,
|
||||||
|
8 => 256000,
|
||||||
|
9 => 320000,
|
||||||
|
10 => 384000,
|
||||||
|
11 => 448000,
|
||||||
|
12 => 512000,
|
||||||
|
13 => 576000,
|
||||||
|
14 => 640000,
|
||||||
|
15 => 768000,
|
||||||
|
16 => 960000,
|
||||||
|
17 => 1024000,
|
||||||
|
18 => 1152000,
|
||||||
|
19 => 1280000,
|
||||||
|
20 => 1344000,
|
||||||
|
21 => 1408000,
|
||||||
|
22 => 1411200,
|
||||||
|
23 => 1472000,
|
||||||
|
24 => 1536000,
|
||||||
|
25 => 1920000,
|
||||||
|
26 => 2048000,
|
||||||
|
27 => 3072000,
|
||||||
|
28 => 3840000,
|
||||||
|
29 => 'open',
|
||||||
|
30 => 'variable',
|
||||||
|
31 => 'lossless'
|
||||||
|
);
|
||||||
|
return (isset($DTSbitrateLookup[$index]) ? $DTSbitrateLookup[$index] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function DTSsampleRateLookup($index) {
|
||||||
|
$DTSsampleRateLookup = array(
|
||||||
|
0 => 'invalid',
|
||||||
|
1 => 8000,
|
||||||
|
2 => 16000,
|
||||||
|
3 => 32000,
|
||||||
|
4 => 'invalid',
|
||||||
|
5 => 'invalid',
|
||||||
|
6 => 11025,
|
||||||
|
7 => 22050,
|
||||||
|
8 => 44100,
|
||||||
|
9 => 'invalid',
|
||||||
|
10 => 'invalid',
|
||||||
|
11 => 12000,
|
||||||
|
12 => 24000,
|
||||||
|
13 => 48000,
|
||||||
|
14 => 'invalid',
|
||||||
|
15 => 'invalid'
|
||||||
|
);
|
||||||
|
return (isset($DTSsampleRateLookup[$index]) ? $DTSsampleRateLookup[$index] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function DTSbitPerSampleLookup($index) {
|
||||||
|
$DTSbitPerSampleLookup = array(
|
||||||
|
0 => 16,
|
||||||
|
1 => 20,
|
||||||
|
2 => 24,
|
||||||
|
3 => 24,
|
||||||
|
);
|
||||||
|
return (isset($DTSbitPerSampleLookup[$index]) ? $DTSbitPerSampleLookup[$index] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function DTSnumChannelsLookup($index) {
|
||||||
|
switch ($index) {
|
||||||
|
case 0:
|
||||||
|
return 1;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
return 2;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
return 3;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
return 4;
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
return 5;
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
case 11:
|
||||||
|
case 12:
|
||||||
|
return 6;
|
||||||
|
break;
|
||||||
|
case 13:
|
||||||
|
return 7;
|
||||||
|
break;
|
||||||
|
case 14:
|
||||||
|
case 15:
|
||||||
|
return 8;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function DTSchannelArrangementLookup($index) {
|
||||||
|
$DTSchannelArrangementLookup = array(
|
||||||
|
0 => 'A',
|
||||||
|
1 => 'A + B (dual mono)',
|
||||||
|
2 => 'L + R (stereo)',
|
||||||
|
3 => '(L+R) + (L-R) (sum-difference)',
|
||||||
|
4 => 'LT + RT (left and right total)',
|
||||||
|
5 => 'C + L + R',
|
||||||
|
6 => 'L + R + S',
|
||||||
|
7 => 'C + L + R + S',
|
||||||
|
8 => 'L + R + SL + SR',
|
||||||
|
9 => 'C + L + R + SL + SR',
|
||||||
|
10 => 'CL + CR + L + R + SL + SR',
|
||||||
|
11 => 'C + L + R+ LR + RR + OV',
|
||||||
|
12 => 'CF + CR + LF + RF + LR + RR',
|
||||||
|
13 => 'CL + C + CR + L + R + SL + SR',
|
||||||
|
14 => 'CL + CR + L + R + SL1 + SL2 + SR1 + SR2',
|
||||||
|
15 => 'CL + C+ CR + L + R + SL + S + SR',
|
||||||
|
);
|
||||||
|
return (isset($DTSchannelArrangementLookup[$index]) ? $DTSchannelArrangementLookup[$index] : 'user-defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function DTSdialogNormalization($index, $version) {
|
||||||
|
switch ($version) {
|
||||||
|
case 7:
|
||||||
|
return 0 - $index;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
return 0 - 16 - $index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
480
app/library/getid3/module.audio.flac.php
Normal file
480
app/library/getid3/module.audio.flac.php
Normal file
|
@ -0,0 +1,480 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.flac.php //
|
||||||
|
// module for analyzing FLAC and OggFLAC audio files //
|
||||||
|
// dependencies: module.audio.ogg.php //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true);
|
||||||
|
|
||||||
|
class getid3_flac extends getid3_handler
|
||||||
|
{
|
||||||
|
var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
// http://flac.sourceforge.net/format.html
|
||||||
|
|
||||||
|
$this->fseek($info['avdataoffset'], SEEK_SET);
|
||||||
|
$StreamMarker = $this->fread(4);
|
||||||
|
$magic = 'fLaC';
|
||||||
|
if ($StreamMarker != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['fileformat'] = 'flac';
|
||||||
|
$info['audio']['dataformat'] = 'flac';
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
$info['audio']['lossless'] = true;
|
||||||
|
|
||||||
|
return $this->FLACparseMETAdata();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function FLACparseMETAdata() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
do {
|
||||||
|
$METAdataBlockOffset = $this->ftell();
|
||||||
|
$METAdataBlockHeader = $this->fread(4);
|
||||||
|
$METAdataLastBlockFlag = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x80);
|
||||||
|
$METAdataBlockType = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x7F;
|
||||||
|
$METAdataBlockLength = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 1, 3));
|
||||||
|
$METAdataBlockTypeText = getid3_flac::FLACmetaBlockTypeLookup($METAdataBlockType);
|
||||||
|
|
||||||
|
if ($METAdataBlockLength < 0) {
|
||||||
|
$info['error'][] = 'corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['flac'][$METAdataBlockTypeText]['raw'] = array();
|
||||||
|
$ThisFileInfo_flac_METAdataBlockTypeText_raw = &$info['flac'][$METAdataBlockTypeText]['raw'];
|
||||||
|
|
||||||
|
$ThisFileInfo_flac_METAdataBlockTypeText_raw['offset'] = $METAdataBlockOffset;
|
||||||
|
$ThisFileInfo_flac_METAdataBlockTypeText_raw['last_meta_block'] = $METAdataLastBlockFlag;
|
||||||
|
$ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type'] = $METAdataBlockType;
|
||||||
|
$ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type_text'] = $METAdataBlockTypeText;
|
||||||
|
$ThisFileInfo_flac_METAdataBlockTypeText_raw['block_length'] = $METAdataBlockLength;
|
||||||
|
if (($METAdataBlockOffset + 4 + $METAdataBlockLength) > $info['avdataend']) {
|
||||||
|
$info['error'][] = 'METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset.' extends beyond end of file';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($METAdataBlockLength < 1) {
|
||||||
|
$info['error'][] = 'METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$METAdataBlockLength.') at offset '.$METAdataBlockOffset.' is invalid';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'] = $this->fread($METAdataBlockLength);
|
||||||
|
$info['avdataoffset'] = $this->ftell();
|
||||||
|
|
||||||
|
switch ($METAdataBlockTypeText) {
|
||||||
|
case 'STREAMINFO': // 0x00
|
||||||
|
if (!$this->FLACparseSTREAMINFO($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'PADDING': // 0x01
|
||||||
|
// ignore
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'APPLICATION': // 0x02
|
||||||
|
if (!$this->FLACparseAPPLICATION($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'SEEKTABLE': // 0x03
|
||||||
|
if (!$this->FLACparseSEEKTABLE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'VORBIS_COMMENT': // 0x04
|
||||||
|
$getid3_temp = new getID3();
|
||||||
|
$getid3_temp->openfile($this->getid3->filename);
|
||||||
|
$getid3_temp->info['avdataoffset'] = $this->ftell() - $METAdataBlockLength;
|
||||||
|
$getid3_temp->info['audio']['dataformat'] = 'flac';
|
||||||
|
$getid3_temp->info['flac'] = $info['flac'];
|
||||||
|
$getid3_ogg = new getid3_ogg($getid3_temp);
|
||||||
|
$getid3_ogg->ParseVorbisCommentsFilepointer();
|
||||||
|
$maybe_copy_keys = array('vendor', 'comments_raw', 'comments', 'replay_gain');
|
||||||
|
foreach ($maybe_copy_keys as $maybe_copy_key) {
|
||||||
|
if (!empty($getid3_temp->info['ogg'][$maybe_copy_key])) {
|
||||||
|
$info['ogg'][$maybe_copy_key] = $getid3_temp->info['ogg'][$maybe_copy_key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($getid3_temp->info['replay_gain'])) {
|
||||||
|
$info['replay_gain'] = $getid3_temp->info['replay_gain'];
|
||||||
|
}
|
||||||
|
unset($getid3_temp, $getid3_ogg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'CUESHEET': // 0x05
|
||||||
|
if (!getid3_flac::FLACparseCUESHEET($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'PICTURE': // 0x06
|
||||||
|
if (!getid3_flac::FLACparsePICTURE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['warning'][] = 'Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while ($METAdataLastBlockFlag === false);
|
||||||
|
|
||||||
|
if (isset($info['flac']['PICTURE'])) {
|
||||||
|
foreach ($info['flac']['PICTURE'] as $key => $valuearray) {
|
||||||
|
if (!empty($valuearray['image_mime']) && !empty($valuearray['data'])) {
|
||||||
|
$info['ogg']['comments']['picture'][] = array('image_mime'=>$valuearray['image_mime'], 'data'=>$valuearray['data']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($info['flac']['STREAMINFO'])) {
|
||||||
|
$info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset'];
|
||||||
|
$info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8);
|
||||||
|
if ($info['flac']['uncompressed_audio_bytes'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt FLAC file: uncompressed_audio_bytes == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// set md5_data_source - built into flac 0.5+
|
||||||
|
if (isset($info['flac']['STREAMINFO']['audio_signature'])) {
|
||||||
|
|
||||||
|
if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
|
||||||
|
|
||||||
|
$info['warning'][] = 'FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['md5_data_source'] = '';
|
||||||
|
$md5 = $info['flac']['STREAMINFO']['audio_signature'];
|
||||||
|
for ($i = 0; $i < strlen($md5); $i++) {
|
||||||
|
$info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
|
||||||
|
unset($info['md5_data_source']);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
|
||||||
|
if ($info['audio']['bits_per_sample'] == 8) {
|
||||||
|
// special case
|
||||||
|
// must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
|
||||||
|
// MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
|
||||||
|
$info['warning'][] = 'FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file';
|
||||||
|
}
|
||||||
|
if (!empty($info['ogg']['vendor'])) {
|
||||||
|
$info['audio']['encoder'] = $info['ogg']['vendor'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function FLACmetaBlockTypeLookup($blocktype) {
|
||||||
|
static $FLACmetaBlockTypeLookup = array();
|
||||||
|
if (empty($FLACmetaBlockTypeLookup)) {
|
||||||
|
$FLACmetaBlockTypeLookup[0] = 'STREAMINFO';
|
||||||
|
$FLACmetaBlockTypeLookup[1] = 'PADDING';
|
||||||
|
$FLACmetaBlockTypeLookup[2] = 'APPLICATION';
|
||||||
|
$FLACmetaBlockTypeLookup[3] = 'SEEKTABLE';
|
||||||
|
$FLACmetaBlockTypeLookup[4] = 'VORBIS_COMMENT';
|
||||||
|
$FLACmetaBlockTypeLookup[5] = 'CUESHEET';
|
||||||
|
$FLACmetaBlockTypeLookup[6] = 'PICTURE';
|
||||||
|
}
|
||||||
|
return (isset($FLACmetaBlockTypeLookup[$blocktype]) ? $FLACmetaBlockTypeLookup[$blocktype] : 'reserved');
|
||||||
|
}
|
||||||
|
|
||||||
|
static function FLACapplicationIDLookup($applicationid) {
|
||||||
|
static $FLACapplicationIDLookup = array();
|
||||||
|
if (empty($FLACapplicationIDLookup)) {
|
||||||
|
// http://flac.sourceforge.net/id.html
|
||||||
|
$FLACapplicationIDLookup[0x46746F6C] = 'flac-tools'; // 'Ftol'
|
||||||
|
$FLACapplicationIDLookup[0x46746F6C] = 'Sound Font FLAC'; // 'SFFL'
|
||||||
|
}
|
||||||
|
return (isset($FLACapplicationIDLookup[$applicationid]) ? $FLACapplicationIDLookup[$applicationid] : 'reserved');
|
||||||
|
}
|
||||||
|
|
||||||
|
static function FLACpictureTypeLookup($type_id) {
|
||||||
|
static $lookup = array (
|
||||||
|
0 => 'Other',
|
||||||
|
1 => '32x32 pixels \'file icon\' (PNG only)',
|
||||||
|
2 => 'Other file icon',
|
||||||
|
3 => 'Cover (front)',
|
||||||
|
4 => 'Cover (back)',
|
||||||
|
5 => 'Leaflet page',
|
||||||
|
6 => 'Media (e.g. label side of CD)',
|
||||||
|
7 => 'Lead artist/lead performer/soloist',
|
||||||
|
8 => 'Artist/performer',
|
||||||
|
9 => 'Conductor',
|
||||||
|
10 => 'Band/Orchestra',
|
||||||
|
11 => 'Composer',
|
||||||
|
12 => 'Lyricist/text writer',
|
||||||
|
13 => 'Recording Location',
|
||||||
|
14 => 'During recording',
|
||||||
|
15 => 'During performance',
|
||||||
|
16 => 'Movie/video screen capture',
|
||||||
|
17 => 'A bright coloured fish',
|
||||||
|
18 => 'Illustration',
|
||||||
|
19 => 'Band/artist logotype',
|
||||||
|
20 => 'Publisher/Studio logotype',
|
||||||
|
);
|
||||||
|
return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
|
||||||
|
}
|
||||||
|
|
||||||
|
function FLACparseSTREAMINFO($METAdataBlockData) {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$offset = 0;
|
||||||
|
$info['flac']['STREAMINFO']['min_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$info['flac']['STREAMINFO']['max_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$info['flac']['STREAMINFO']['min_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3));
|
||||||
|
$offset += 3;
|
||||||
|
$info['flac']['STREAMINFO']['max_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3));
|
||||||
|
$offset += 3;
|
||||||
|
|
||||||
|
$SampleRateChannelsSampleBitsStreamSamples = getid3_lib::BigEndian2Bin(substr($METAdataBlockData, $offset, 8));
|
||||||
|
$info['flac']['STREAMINFO']['sample_rate'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 0, 20));
|
||||||
|
$info['flac']['STREAMINFO']['channels'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 20, 3)) + 1;
|
||||||
|
$info['flac']['STREAMINFO']['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 23, 5)) + 1;
|
||||||
|
$info['flac']['STREAMINFO']['samples_stream'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 28, 36));
|
||||||
|
$offset += 8;
|
||||||
|
|
||||||
|
$info['flac']['STREAMINFO']['audio_signature'] = substr($METAdataBlockData, $offset, 16);
|
||||||
|
$offset += 16;
|
||||||
|
|
||||||
|
if (!empty($info['flac']['STREAMINFO']['sample_rate'])) {
|
||||||
|
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
$info['audio']['sample_rate'] = $info['flac']['STREAMINFO']['sample_rate'];
|
||||||
|
$info['audio']['channels'] = $info['flac']['STREAMINFO']['channels'];
|
||||||
|
$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
|
||||||
|
$info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate'];
|
||||||
|
if ($info['playtime_seconds'] > 0) {
|
||||||
|
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$info['error'][] = 'Corrupt METAdata block: STREAMINFO';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
unset($info['flac']['STREAMINFO']['raw']);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function FLACparseAPPLICATION($METAdataBlockData) {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$offset = 0;
|
||||||
|
$ApplicationID = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$info['flac']['APPLICATION'][$ApplicationID]['name'] = getid3_flac::FLACapplicationIDLookup($ApplicationID);
|
||||||
|
$info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($METAdataBlockData, $offset);
|
||||||
|
$offset = $METAdataBlockLength;
|
||||||
|
|
||||||
|
unset($info['flac']['APPLICATION']['raw']);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function FLACparseSEEKTABLE($METAdataBlockData) {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$offset = 0;
|
||||||
|
$METAdataBlockLength = strlen($METAdataBlockData);
|
||||||
|
$placeholderpattern = str_repeat("\xFF", 8);
|
||||||
|
while ($offset < $METAdataBlockLength) {
|
||||||
|
$SampleNumberString = substr($METAdataBlockData, $offset, 8);
|
||||||
|
$offset += 8;
|
||||||
|
if ($SampleNumberString == $placeholderpattern) {
|
||||||
|
|
||||||
|
// placeholder point
|
||||||
|
getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1);
|
||||||
|
$offset += 10;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString);
|
||||||
|
$info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8));
|
||||||
|
$offset += 8;
|
||||||
|
$info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($info['flac']['SEEKTABLE']['raw']);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FLACparseCUESHEET($METAdataBlockData) {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
$offset = 0;
|
||||||
|
$info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($METAdataBlockData, $offset, 128), "\0");
|
||||||
|
$offset += 128;
|
||||||
|
$info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8));
|
||||||
|
$offset += 8;
|
||||||
|
$info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)) & 0x80);
|
||||||
|
$offset += 1;
|
||||||
|
|
||||||
|
$offset += 258; // reserved
|
||||||
|
|
||||||
|
$info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
|
||||||
|
for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) {
|
||||||
|
$TrackSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8));
|
||||||
|
$offset += 8;
|
||||||
|
$TrackNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
|
||||||
|
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset;
|
||||||
|
|
||||||
|
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($METAdataBlockData, $offset, 12);
|
||||||
|
$offset += 12;
|
||||||
|
|
||||||
|
$TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80);
|
||||||
|
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40);
|
||||||
|
|
||||||
|
$offset += 13; // reserved
|
||||||
|
|
||||||
|
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
|
||||||
|
for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) {
|
||||||
|
$IndexSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8));
|
||||||
|
$offset += 8;
|
||||||
|
$IndexNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
|
||||||
|
$offset += 3; // reserved
|
||||||
|
|
||||||
|
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($info['flac']['CUESHEET']['raw']);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function FLACparsePICTURE($meta_data_block_data) {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
$picture = &$info['flac']['PICTURE'][sizeof($info['flac']['PICTURE']) - 1];
|
||||||
|
$picture['offset'] = $info['flac']['PICTURE']['raw']['offset'];
|
||||||
|
unset($info['flac']['PICTURE']['raw']);
|
||||||
|
|
||||||
|
$offset = 0;
|
||||||
|
|
||||||
|
$picture['typeid'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
|
||||||
|
$picture['type'] = getid3_flac::FLACpictureTypeLookup($picture['typeid']);
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$picture['image_mime'] = substr($meta_data_block_data, $offset, $length);
|
||||||
|
$offset += $length;
|
||||||
|
|
||||||
|
$length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$picture['description'] = substr($meta_data_block_data, $offset, $length);
|
||||||
|
$offset += $length;
|
||||||
|
|
||||||
|
$picture['width'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$picture['height'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$picture['color_depth'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$picture['colors_indexed'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$picture['data'] = substr($meta_data_block_data, $offset, $length);
|
||||||
|
$offset += $length;
|
||||||
|
$picture['data_length'] = strlen($picture['data']);
|
||||||
|
|
||||||
|
|
||||||
|
do {
|
||||||
|
if ($this->inline_attachments === false) {
|
||||||
|
// skip entirely
|
||||||
|
unset($picture['data']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($this->inline_attachments === true) {
|
||||||
|
// great
|
||||||
|
} elseif (is_int($this->inline_attachments)) {
|
||||||
|
if ($this->inline_attachments < $picture['data_length']) {
|
||||||
|
// too big, skip
|
||||||
|
$info['warning'][] = 'attachment at '.$picture['offset'].' is too large to process inline ('.number_format($picture['data_length']).' bytes)';
|
||||||
|
unset($picture['data']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} elseif (is_string($this->inline_attachments)) {
|
||||||
|
$this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
|
||||||
|
if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
|
||||||
|
// cannot write, skip
|
||||||
|
$info['warning'][] = 'attachment at '.$picture['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
|
||||||
|
unset($picture['data']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we get this far, must be OK
|
||||||
|
if (is_string($this->inline_attachments)) {
|
||||||
|
$destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$picture['offset'];
|
||||||
|
if (!file_exists($destination_filename) || is_writable($destination_filename)) {
|
||||||
|
file_put_contents($destination_filename, $picture['data']);
|
||||||
|
} else {
|
||||||
|
$info['warning'][] = 'attachment at '.$picture['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
|
||||||
|
}
|
||||||
|
$picture['data_filename'] = $destination_filename;
|
||||||
|
unset($picture['data']);
|
||||||
|
} else {
|
||||||
|
if (!isset($info['flac']['comments']['picture'])) {
|
||||||
|
$info['flac']['comments']['picture'] = array();
|
||||||
|
}
|
||||||
|
$info['flac']['comments']['picture'][] = array('data'=>$picture['data'], 'image_mime'=>$picture['image_mime']);
|
||||||
|
}
|
||||||
|
} while (false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
229
app/library/getid3/module.audio.la.php
Normal file
229
app/library/getid3/module.audio.la.php
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.la.php //
|
||||||
|
// module for analyzing LA (LosslessAudio) audio files //
|
||||||
|
// dependencies: module.audio.riff.php //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||||
|
|
||||||
|
class getid3_la extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$offset = 0;
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$rawdata = fread($this->getid3->fp, $this->getid3->fread_buffer_size());
|
||||||
|
|
||||||
|
switch (substr($rawdata, $offset, 4)) {
|
||||||
|
case 'LA02':
|
||||||
|
case 'LA03':
|
||||||
|
case 'LA04':
|
||||||
|
$info['fileformat'] = 'la';
|
||||||
|
$info['audio']['dataformat'] = 'la';
|
||||||
|
$info['audio']['lossless'] = true;
|
||||||
|
|
||||||
|
$info['la']['version_major'] = (int) substr($rawdata, $offset + 2, 1);
|
||||||
|
$info['la']['version_minor'] = (int) substr($rawdata, $offset + 3, 1);
|
||||||
|
$info['la']['version'] = (float) $info['la']['version_major'] + ($info['la']['version_minor'] / 10);
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$info['la']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
if ($info['la']['uncompressed_size'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt LA file: uncompressed_size == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$WAVEchunk = substr($rawdata, $offset, 4);
|
||||||
|
if ($WAVEchunk !== 'WAVE') {
|
||||||
|
$info['error'][] = 'Expected "WAVE" ('.getid3_lib::PrintHexBytes('WAVE').') at offset '.$offset.', found "'.$WAVEchunk.'" ('.getid3_lib::PrintHexBytes($WAVEchunk).') instead.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$info['la']['fmt_size'] = 24;
|
||||||
|
if ($info['la']['version'] >= 0.3) {
|
||||||
|
|
||||||
|
$info['la']['fmt_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||||
|
$info['la']['header_size'] = 49 + $info['la']['fmt_size'] - 24;
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// version 0.2 didn't support additional data blocks
|
||||||
|
$info['la']['header_size'] = 41;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$fmt_chunk = substr($rawdata, $offset, 4);
|
||||||
|
if ($fmt_chunk !== 'fmt ') {
|
||||||
|
$info['error'][] = 'Expected "fmt " ('.getid3_lib::PrintHexBytes('fmt ').') at offset '.$offset.', found "'.$fmt_chunk.'" ('.getid3_lib::PrintHexBytes($fmt_chunk).') instead.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$offset += 4;
|
||||||
|
$fmt_size = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$info['la']['raw']['format'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
|
||||||
|
$info['la']['channels'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
if ($info['la']['channels'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt LA file: channels == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['la']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
if ($info['la']['sample_rate'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt LA file: sample_rate == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['la']['bytes_per_second'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$info['la']['bytes_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$info['la']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
|
||||||
|
$info['la']['samples'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$info['la']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
$info['la']['flags']['seekable'] = (bool) ($info['la']['raw']['flags'] & 0x01);
|
||||||
|
if ($info['la']['version'] >= 0.4) {
|
||||||
|
$info['la']['flags']['high_compression'] = (bool) ($info['la']['raw']['flags'] & 0x02);
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['la']['original_crc'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
// mikeØbevin*de
|
||||||
|
// Basically, the blocksize/seekevery are 61440/19 in La0.4 and 73728/16
|
||||||
|
// in earlier versions. A seekpoint is added every blocksize * seekevery
|
||||||
|
// samples, so 4 * int(totalSamples / (blockSize * seekEvery)) should
|
||||||
|
// give the number of bytes used for the seekpoints. Of course, if seeking
|
||||||
|
// is disabled, there are no seekpoints stored.
|
||||||
|
if ($info['la']['version'] >= 0.4) {
|
||||||
|
$info['la']['blocksize'] = 61440;
|
||||||
|
$info['la']['seekevery'] = 19;
|
||||||
|
} else {
|
||||||
|
$info['la']['blocksize'] = 73728;
|
||||||
|
$info['la']['seekevery'] = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['la']['seekpoint_count'] = 0;
|
||||||
|
if ($info['la']['flags']['seekable']) {
|
||||||
|
$info['la']['seekpoint_count'] = floor($info['la']['samples'] / ($info['la']['blocksize'] * $info['la']['seekevery']));
|
||||||
|
|
||||||
|
for ($i = 0; $i < $info['la']['seekpoint_count']; $i++) {
|
||||||
|
$info['la']['seekpoints'][] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($info['la']['version'] >= 0.3) {
|
||||||
|
|
||||||
|
// Following the main header information, the program outputs all of the
|
||||||
|
// seekpoints. Following these is what I called the 'footer start',
|
||||||
|
// i.e. the position immediately after the La audio data is finished.
|
||||||
|
$info['la']['footerstart'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
if ($info['la']['footerstart'] > $info['filesize']) {
|
||||||
|
$info['warning'][] = 'FooterStart value points to offset '.$info['la']['footerstart'].' which is beyond end-of-file ('.$info['filesize'].')';
|
||||||
|
$info['la']['footerstart'] = $info['filesize'];
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// La v0.2 didn't have FooterStart value
|
||||||
|
$info['la']['footerstart'] = $info['avdataend'];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($info['la']['footerstart'] < $info['avdataend']) {
|
||||||
|
if ($RIFFtempfilename = tempnam(GETID3_TEMP_DIR, 'id3')) {
|
||||||
|
if ($RIFF_fp = fopen($RIFFtempfilename, 'w+b')) {
|
||||||
|
$RIFFdata = 'WAVE';
|
||||||
|
if ($info['la']['version'] == 0.2) {
|
||||||
|
$RIFFdata .= substr($rawdata, 12, 24);
|
||||||
|
} else {
|
||||||
|
$RIFFdata .= substr($rawdata, 16, 24);
|
||||||
|
}
|
||||||
|
if ($info['la']['footerstart'] < $info['avdataend']) {
|
||||||
|
fseek($this->getid3->fp, $info['la']['footerstart'], SEEK_SET);
|
||||||
|
$RIFFdata .= fread($this->getid3->fp, $info['avdataend'] - $info['la']['footerstart']);
|
||||||
|
}
|
||||||
|
$RIFFdata = 'RIFF'.getid3_lib::LittleEndian2String(strlen($RIFFdata), 4, false).$RIFFdata;
|
||||||
|
fwrite($RIFF_fp, $RIFFdata, strlen($RIFFdata));
|
||||||
|
fclose($RIFF_fp);
|
||||||
|
|
||||||
|
$getid3_temp = new getID3();
|
||||||
|
$getid3_temp->openfile($RIFFtempfilename);
|
||||||
|
$getid3_riff = new getid3_riff($getid3_temp);
|
||||||
|
$getid3_riff->Analyze();
|
||||||
|
|
||||||
|
if (empty($getid3_temp->info['error'])) {
|
||||||
|
$info['riff'] = $getid3_temp->info['riff'];
|
||||||
|
} else {
|
||||||
|
$info['warning'][] = 'Error parsing RIFF portion of La file: '.implode($getid3_temp->info['error']);
|
||||||
|
}
|
||||||
|
unset($getid3_temp, $getid3_riff);
|
||||||
|
}
|
||||||
|
unlink($RIFFtempfilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// $info['avdataoffset'] should be zero to begin with, but just in case it's not, include the addition anyway
|
||||||
|
$info['avdataend'] = $info['avdataoffset'] + $info['la']['footerstart'];
|
||||||
|
$info['avdataoffset'] = $info['avdataoffset'] + $offset;
|
||||||
|
|
||||||
|
//$info['la']['codec'] = RIFFwFormatTagLookup($info['la']['raw']['format']);
|
||||||
|
$info['la']['compression_ratio'] = (float) (($info['avdataend'] - $info['avdataoffset']) / $info['la']['uncompressed_size']);
|
||||||
|
$info['playtime_seconds'] = (float) ($info['la']['samples'] / $info['la']['sample_rate']) / $info['la']['channels'];
|
||||||
|
if ($info['playtime_seconds'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt LA file: playtime_seconds == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds'];
|
||||||
|
//$info['audio']['codec'] = $info['la']['codec'];
|
||||||
|
$info['audio']['bits_per_sample'] = $info['la']['bits_per_sample'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (substr($rawdata, $offset, 2) == 'LA') {
|
||||||
|
$info['error'][] = 'This version of getID3() ['.$this->getid3->version().'] does not support LA version '.substr($rawdata, $offset + 2, 1).'.'.substr($rawdata, $offset + 3, 1).' which this appears to be - check http://getid3.sourceforge.net for updates.';
|
||||||
|
} else {
|
||||||
|
$info['error'][] = 'Not a LA (Lossless-Audio) file';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['audio']['channels'] = $info['la']['channels'];
|
||||||
|
$info['audio']['sample_rate'] = (int) $info['la']['sample_rate'];
|
||||||
|
$info['audio']['encoder'] = 'LA v'.$info['la']['version'];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
130
app/library/getid3/module.audio.lpac.php
Normal file
130
app/library/getid3/module.audio.lpac.php
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.lpac.php //
|
||||||
|
// module for analyzing LPAC Audio files //
|
||||||
|
// dependencies: module.audio-video.riff.php //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||||
|
|
||||||
|
class getid3_lpac extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$LPACheader = fread($this->getid3->fp, 14);
|
||||||
|
if (substr($LPACheader, 0, 4) != 'LPAC') {
|
||||||
|
$info['error'][] = 'Expected "LPAC" at offset '.$info['avdataoffset'].', found "'.$StreamMarker.'"';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['avdataoffset'] += 14;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'lpac';
|
||||||
|
$info['audio']['dataformat'] = 'lpac';
|
||||||
|
$info['audio']['lossless'] = true;
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
|
||||||
|
$info['lpac']['file_version'] = getid3_lib::BigEndian2Int(substr($LPACheader, 4, 1));
|
||||||
|
$flags['audio_type'] = getid3_lib::BigEndian2Int(substr($LPACheader, 5, 1));
|
||||||
|
$info['lpac']['total_samples']= getid3_lib::BigEndian2Int(substr($LPACheader, 6, 4));
|
||||||
|
$flags['parameters'] = getid3_lib::BigEndian2Int(substr($LPACheader, 10, 4));
|
||||||
|
|
||||||
|
$info['lpac']['flags']['is_wave'] = (bool) ($flags['audio_type'] & 0x40);
|
||||||
|
$info['lpac']['flags']['stereo'] = (bool) ($flags['audio_type'] & 0x04);
|
||||||
|
$info['lpac']['flags']['24_bit'] = (bool) ($flags['audio_type'] & 0x02);
|
||||||
|
$info['lpac']['flags']['16_bit'] = (bool) ($flags['audio_type'] & 0x01);
|
||||||
|
|
||||||
|
if ($info['lpac']['flags']['24_bit'] && $info['lpac']['flags']['16_bit']) {
|
||||||
|
$info['warning'][] = '24-bit and 16-bit flags cannot both be set';
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['lpac']['flags']['fast_compress'] = (bool) ($flags['parameters'] & 0x40000000);
|
||||||
|
$info['lpac']['flags']['random_access'] = (bool) ($flags['parameters'] & 0x08000000);
|
||||||
|
$info['lpac']['block_length'] = pow(2, (($flags['parameters'] & 0x07000000) >> 24)) * 256;
|
||||||
|
$info['lpac']['flags']['adaptive_prediction_order'] = (bool) ($flags['parameters'] & 0x00800000);
|
||||||
|
$info['lpac']['flags']['adaptive_quantization'] = (bool) ($flags['parameters'] & 0x00400000);
|
||||||
|
$info['lpac']['flags']['joint_stereo'] = (bool) ($flags['parameters'] & 0x00040000);
|
||||||
|
$info['lpac']['quantization'] = ($flags['parameters'] & 0x00001F00) >> 8;
|
||||||
|
$info['lpac']['max_prediction_order'] = ($flags['parameters'] & 0x0000003F);
|
||||||
|
|
||||||
|
if ($info['lpac']['flags']['fast_compress'] && ($info['lpac']['max_prediction_order'] != 3)) {
|
||||||
|
$info['warning'][] = 'max_prediction_order expected to be "3" if fast_compress is true, actual value is "'.$info['lpac']['max_prediction_order'].'"';
|
||||||
|
}
|
||||||
|
switch ($info['lpac']['file_version']) {
|
||||||
|
case 6:
|
||||||
|
if ($info['lpac']['flags']['adaptive_quantization']) {
|
||||||
|
$info['warning'][] = 'adaptive_quantization expected to be false in LPAC file stucture v6, actually true';
|
||||||
|
}
|
||||||
|
if ($info['lpac']['quantization'] != 20) {
|
||||||
|
$info['warning'][] = 'Quantization expected to be 20 in LPAC file stucture v6, actually '.$info['lpac']['flags']['Q'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
//$info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] only supports LPAC file format version 6, this file is version '.$info['lpac']['file_version'].' - please report to info@getid3.org';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$getid3_temp = new getID3();
|
||||||
|
$getid3_temp->openfile($this->getid3->filename);
|
||||||
|
$getid3_temp->info = $info;
|
||||||
|
$getid3_riff = new getid3_riff($getid3_temp);
|
||||||
|
$getid3_riff->Analyze();
|
||||||
|
$info['avdataoffset'] = $getid3_temp->info['avdataoffset'];
|
||||||
|
$info['riff'] = $getid3_temp->info['riff'];
|
||||||
|
$info['error'] = $getid3_temp->info['error'];
|
||||||
|
$info['warning'] = $getid3_temp->info['warning'];
|
||||||
|
$info['lpac']['comments']['comment'] = $getid3_temp->info['comments'];
|
||||||
|
$info['audio']['sample_rate'] = $getid3_temp->info['audio']['sample_rate'];
|
||||||
|
unset($getid3_temp, $getid3_riff);
|
||||||
|
|
||||||
|
$info['audio']['channels'] = ($info['lpac']['flags']['stereo'] ? 2 : 1);
|
||||||
|
|
||||||
|
if ($info['lpac']['flags']['24_bit']) {
|
||||||
|
$info['audio']['bits_per_sample'] = $info['riff']['audio'][0]['bits_per_sample'];
|
||||||
|
} elseif ($info['lpac']['flags']['16_bit']) {
|
||||||
|
$info['audio']['bits_per_sample'] = 16;
|
||||||
|
} else {
|
||||||
|
$info['audio']['bits_per_sample'] = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($info['lpac']['flags']['fast_compress']) {
|
||||||
|
// fast
|
||||||
|
$info['audio']['encoder_options'] = '-1';
|
||||||
|
} else {
|
||||||
|
switch ($info['lpac']['max_prediction_order']) {
|
||||||
|
case 20: // simple
|
||||||
|
$info['audio']['encoder_options'] = '-2';
|
||||||
|
break;
|
||||||
|
case 30: // medium
|
||||||
|
$info['audio']['encoder_options'] = '-3';
|
||||||
|
break;
|
||||||
|
case 40: // high
|
||||||
|
$info['audio']['encoder_options'] = '-4';
|
||||||
|
break;
|
||||||
|
case 60: // extrahigh
|
||||||
|
$info['audio']['encoder_options'] = '-5';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = $info['lpac']['total_samples'] / $info['audio']['sample_rate'];
|
||||||
|
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
526
app/library/getid3/module.audio.midi.php
Normal file
526
app/library/getid3/module.audio.midi.php
Normal file
|
@ -0,0 +1,526 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.midi.php //
|
||||||
|
// module for Midi Audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
define('GETID3_MIDI_MAGIC_MTHD', 'MThd'); // MIDI file header magic
|
||||||
|
define('GETID3_MIDI_MAGIC_MTRK', 'MTrk'); // MIDI track header magic
|
||||||
|
|
||||||
|
class getid3_midi extends getid3_handler
|
||||||
|
{
|
||||||
|
var $scanwholefile = true;
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$info['midi']['raw'] = array();
|
||||||
|
$thisfile_midi = &$info['midi'];
|
||||||
|
$thisfile_midi_raw = &$thisfile_midi['raw'];
|
||||||
|
|
||||||
|
$info['fileformat'] = 'midi';
|
||||||
|
$info['audio']['dataformat'] = 'midi';
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$MIDIdata = fread($this->getid3->fp, $this->getid3->fread_buffer_size());
|
||||||
|
$offset = 0;
|
||||||
|
$MIDIheaderID = substr($MIDIdata, $offset, 4); // 'MThd'
|
||||||
|
if ($MIDIheaderID != GETID3_MIDI_MAGIC_MTHD) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTHD).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($MIDIheaderID).'"';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_midi_raw['headersize'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_midi_raw['fileformat'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_midi_raw['tracks'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_midi_raw['ticksperqnote'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) {
|
||||||
|
while ((strlen($MIDIdata) - $offset) < 8) {
|
||||||
|
$MIDIdata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size());
|
||||||
|
}
|
||||||
|
$trackID = substr($MIDIdata, $offset, 4);
|
||||||
|
$offset += 4;
|
||||||
|
if ($trackID == GETID3_MIDI_MAGIC_MTRK) {
|
||||||
|
$tracksize = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
// $thisfile_midi['tracks'][$i]['size'] = $tracksize;
|
||||||
|
$trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize);
|
||||||
|
$offset += $tracksize;
|
||||||
|
} else {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTRK).'" at '.($offset - 4).', found "'.getid3_lib::PrintHexBytes($trackID).'" instead';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($trackdataarray) || !is_array($trackdataarray)) {
|
||||||
|
$info['error'][] = 'Cannot find MIDI track information';
|
||||||
|
unset($thisfile_midi);
|
||||||
|
unset($info['fileformat']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->scanwholefile) { // this can take quite a long time, so have the option to bypass it if speed is very important
|
||||||
|
$thisfile_midi['totalticks'] = 0;
|
||||||
|
$info['playtime_seconds'] = 0;
|
||||||
|
$CurrentMicroSecondsPerBeat = 500000; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
|
||||||
|
$CurrentBeatsPerMinute = 120; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
|
||||||
|
$MicroSecondsPerQuarterNoteAfter = array ();
|
||||||
|
|
||||||
|
foreach ($trackdataarray as $tracknumber => $trackdata) {
|
||||||
|
|
||||||
|
$eventsoffset = 0;
|
||||||
|
$LastIssuedMIDIcommand = 0;
|
||||||
|
$LastIssuedMIDIchannel = 0;
|
||||||
|
$CumulativeDeltaTime = 0;
|
||||||
|
$TicksAtCurrentBPM = 0;
|
||||||
|
while ($eventsoffset < strlen($trackdata)) {
|
||||||
|
$eventid = 0;
|
||||||
|
if (isset($MIDIevents[$tracknumber]) && is_array($MIDIevents[$tracknumber])) {
|
||||||
|
$eventid = count($MIDIevents[$tracknumber]);
|
||||||
|
}
|
||||||
|
$deltatime = 0;
|
||||||
|
for ($i = 0; $i < 4; $i++) {
|
||||||
|
$deltatimebyte = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
$deltatime = ($deltatime << 7) + ($deltatimebyte & 0x7F);
|
||||||
|
if ($deltatimebyte & 0x80) {
|
||||||
|
// another byte follows
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$CumulativeDeltaTime += $deltatime;
|
||||||
|
$TicksAtCurrentBPM += $deltatime;
|
||||||
|
$MIDIevents[$tracknumber][$eventid]['deltatime'] = $deltatime;
|
||||||
|
$MIDI_event_channel = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
if ($MIDI_event_channel & 0x80) {
|
||||||
|
// OK, normal event - MIDI command has MSB set
|
||||||
|
$LastIssuedMIDIcommand = $MIDI_event_channel >> 4;
|
||||||
|
$LastIssuedMIDIchannel = $MIDI_event_channel & 0x0F;
|
||||||
|
} else {
|
||||||
|
// running event - assume last command
|
||||||
|
$eventsoffset--;
|
||||||
|
}
|
||||||
|
$MIDIevents[$tracknumber][$eventid]['eventid'] = $LastIssuedMIDIcommand;
|
||||||
|
$MIDIevents[$tracknumber][$eventid]['channel'] = $LastIssuedMIDIchannel;
|
||||||
|
if ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x08) { // Note off (key is released)
|
||||||
|
|
||||||
|
$notenumber = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
$velocity = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
|
||||||
|
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x09) { // Note on (key is pressed)
|
||||||
|
|
||||||
|
$notenumber = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
$velocity = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
|
||||||
|
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0A) { // Key after-touch
|
||||||
|
|
||||||
|
$notenumber = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
$velocity = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
|
||||||
|
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0B) { // Control Change
|
||||||
|
|
||||||
|
$controllernum = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
$newvalue = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
|
||||||
|
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0C) { // Program (patch) change
|
||||||
|
|
||||||
|
$newprogramnum = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
|
||||||
|
$thisfile_midi_raw['track'][$tracknumber]['instrumentid'] = $newprogramnum;
|
||||||
|
if ($tracknumber == 10) {
|
||||||
|
$thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIpercussionLookup($newprogramnum);
|
||||||
|
} else {
|
||||||
|
$thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIinstrumentLookup($newprogramnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0D) { // Channel after-touch
|
||||||
|
|
||||||
|
$channelnumber = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
|
||||||
|
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0E) { // Pitch wheel change (2000H is normal or no change)
|
||||||
|
|
||||||
|
$changeLSB = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
$changeMSB = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
$pitchwheelchange = (($changeMSB & 0x7F) << 7) & ($changeLSB & 0x7F);
|
||||||
|
|
||||||
|
} elseif (($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0F) && ($MIDIevents[$tracknumber][$eventid]['channel'] == 0x0F)) {
|
||||||
|
|
||||||
|
$METAeventCommand = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
$METAeventLength = ord(substr($trackdata, $eventsoffset++, 1));
|
||||||
|
$METAeventData = substr($trackdata, $eventsoffset, $METAeventLength);
|
||||||
|
$eventsoffset += $METAeventLength;
|
||||||
|
switch ($METAeventCommand) {
|
||||||
|
case 0x00: // Set track sequence number
|
||||||
|
$track_sequence_number = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['seqno'] = $track_sequence_number;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01: // Text: generic
|
||||||
|
$text_generic = substr($METAeventData, 0, $METAeventLength);
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['text'] = $text_generic;
|
||||||
|
$thisfile_midi['comments']['comment'][] = $text_generic;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x02: // Text: copyright
|
||||||
|
$text_copyright = substr($METAeventData, 0, $METAeventLength);
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['copyright'] = $text_copyright;
|
||||||
|
$thisfile_midi['comments']['copyright'][] = $text_copyright;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x03: // Text: track name
|
||||||
|
$text_trackname = substr($METAeventData, 0, $METAeventLength);
|
||||||
|
$thisfile_midi_raw['track'][$tracknumber]['name'] = $text_trackname;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x04: // Text: track instrument name
|
||||||
|
$text_instrument = substr($METAeventData, 0, $METAeventLength);
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['instrument'] = $text_instrument;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x05: // Text: lyrics
|
||||||
|
$text_lyrics = substr($METAeventData, 0, $METAeventLength);
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['lyrics'] = $text_lyrics;
|
||||||
|
if (!isset($thisfile_midi['lyrics'])) {
|
||||||
|
$thisfile_midi['lyrics'] = '';
|
||||||
|
}
|
||||||
|
$thisfile_midi['lyrics'] .= $text_lyrics."\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x06: // Text: marker
|
||||||
|
$text_marker = substr($METAeventData, 0, $METAeventLength);
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['marker'] = $text_marker;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x07: // Text: cue point
|
||||||
|
$text_cuepoint = substr($METAeventData, 0, $METAeventLength);
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['cuepoint'] = $text_cuepoint;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x2F: // End Of Track
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['EOT'] = $CumulativeDeltaTime;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x51: // Tempo: microseconds / quarter note
|
||||||
|
$CurrentMicroSecondsPerBeat = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
|
||||||
|
if ($CurrentMicroSecondsPerBeat == 0) {
|
||||||
|
$info['error'][] = 'Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat;
|
||||||
|
$CurrentBeatsPerMinute = (1000000 / $CurrentMicroSecondsPerBeat) * 60;
|
||||||
|
$MicroSecondsPerQuarterNoteAfter[$CumulativeDeltaTime] = $CurrentMicroSecondsPerBeat;
|
||||||
|
$TicksAtCurrentBPM = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x58: // Time signature
|
||||||
|
$timesig_numerator = getid3_lib::BigEndian2Int($METAeventData{0});
|
||||||
|
$timesig_denominator = pow(2, getid3_lib::BigEndian2Int($METAeventData{1})); // $02 -> x/4, $03 -> x/8, etc
|
||||||
|
$timesig_32inqnote = getid3_lib::BigEndian2Int($METAeventData{2}); // number of 32nd notes to the quarter note
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_32inqnote'] = $timesig_32inqnote;
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_numerator'] = $timesig_numerator;
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_denominator'] = $timesig_denominator;
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_text'] = $timesig_numerator.'/'.$timesig_denominator;
|
||||||
|
$thisfile_midi['timesignature'][] = $timesig_numerator.'/'.$timesig_denominator;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x59: // Keysignature
|
||||||
|
$keysig_sharpsflats = getid3_lib::BigEndian2Int($METAeventData{0});
|
||||||
|
if ($keysig_sharpsflats & 0x80) {
|
||||||
|
// (-7 -> 7 flats, 0 ->key of C, 7 -> 7 sharps)
|
||||||
|
$keysig_sharpsflats -= 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
$keysig_majorminor = getid3_lib::BigEndian2Int($METAeventData{1}); // 0 -> major, 1 -> minor
|
||||||
|
$keysigs = array(-7=>'Cb', -6=>'Gb', -5=>'Db', -4=>'Ab', -3=>'Eb', -2=>'Bb', -1=>'F', 0=>'C', 1=>'G', 2=>'D', 3=>'A', 4=>'E', 5=>'B', 6=>'F#', 7=>'C#');
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_sharps'] = (($keysig_sharpsflats > 0) ? abs($keysig_sharpsflats) : 0);
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_flats'] = (($keysig_sharpsflats < 0) ? abs($keysig_sharpsflats) : 0);
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] = (bool) $keysig_majorminor;
|
||||||
|
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_text'] = $keysigs[$keysig_sharpsflats].' '.($thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] ? 'minor' : 'major');
|
||||||
|
|
||||||
|
// $keysigs[$keysig_sharpsflats] gets an int key (correct) - $keysigs["$keysig_sharpsflats"] gets a string key (incorrect)
|
||||||
|
$thisfile_midi['keysignature'][] = $keysigs[$keysig_sharpsflats].' '.((bool) $keysig_majorminor ? 'minor' : 'major');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x7F: // Sequencer specific information
|
||||||
|
$custom_data = substr($METAeventData, 0, $METAeventLength);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['warning'][] = 'Unhandled META Event Command: '.$METAeventCommand;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['warning'][] = 'Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel'];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (($tracknumber > 0) || (count($trackdataarray) == 1)) {
|
||||||
|
$thisfile_midi['totalticks'] = max($thisfile_midi['totalticks'], $CumulativeDeltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$previoustickoffset = null;
|
||||||
|
|
||||||
|
ksort($MicroSecondsPerQuarterNoteAfter);
|
||||||
|
foreach ($MicroSecondsPerQuarterNoteAfter as $tickoffset => $microsecondsperbeat) {
|
||||||
|
if (is_null($previoustickoffset)) {
|
||||||
|
$prevmicrosecondsperbeat = $microsecondsperbeat;
|
||||||
|
$previoustickoffset = $tickoffset;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($thisfile_midi['totalticks'] > $tickoffset) {
|
||||||
|
|
||||||
|
if ($thisfile_midi_raw['ticksperqnote'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt MIDI file: ticksperqnote == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000);
|
||||||
|
|
||||||
|
$prevmicrosecondsperbeat = $microsecondsperbeat;
|
||||||
|
$previoustickoffset = $tickoffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($thisfile_midi['totalticks'] > $previoustickoffset) {
|
||||||
|
|
||||||
|
if ($thisfile_midi_raw['ticksperqnote'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt MIDI file: ticksperqnote == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['playtime_seconds'] += (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($microsecondsperbeat / 1000000);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!empty($info['playtime_seconds'])) {
|
||||||
|
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($thisfile_midi['lyrics'])) {
|
||||||
|
$thisfile_midi['comments']['lyrics'][] = $thisfile_midi['lyrics'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GeneralMIDIinstrumentLookup($instrumentid) {
|
||||||
|
|
||||||
|
$begin = __LINE__;
|
||||||
|
|
||||||
|
/** This is not a comment!
|
||||||
|
|
||||||
|
0 Acoustic Grand
|
||||||
|
1 Bright Acoustic
|
||||||
|
2 Electric Grand
|
||||||
|
3 Honky-Tonk
|
||||||
|
4 Electric Piano 1
|
||||||
|
5 Electric Piano 2
|
||||||
|
6 Harpsichord
|
||||||
|
7 Clavier
|
||||||
|
8 Celesta
|
||||||
|
9 Glockenspiel
|
||||||
|
10 Music Box
|
||||||
|
11 Vibraphone
|
||||||
|
12 Marimba
|
||||||
|
13 Xylophone
|
||||||
|
14 Tubular Bells
|
||||||
|
15 Dulcimer
|
||||||
|
16 Drawbar Organ
|
||||||
|
17 Percussive Organ
|
||||||
|
18 Rock Organ
|
||||||
|
19 Church Organ
|
||||||
|
20 Reed Organ
|
||||||
|
21 Accordian
|
||||||
|
22 Harmonica
|
||||||
|
23 Tango Accordian
|
||||||
|
24 Acoustic Guitar (nylon)
|
||||||
|
25 Acoustic Guitar (steel)
|
||||||
|
26 Electric Guitar (jazz)
|
||||||
|
27 Electric Guitar (clean)
|
||||||
|
28 Electric Guitar (muted)
|
||||||
|
29 Overdriven Guitar
|
||||||
|
30 Distortion Guitar
|
||||||
|
31 Guitar Harmonics
|
||||||
|
32 Acoustic Bass
|
||||||
|
33 Electric Bass (finger)
|
||||||
|
34 Electric Bass (pick)
|
||||||
|
35 Fretless Bass
|
||||||
|
36 Slap Bass 1
|
||||||
|
37 Slap Bass 2
|
||||||
|
38 Synth Bass 1
|
||||||
|
39 Synth Bass 2
|
||||||
|
40 Violin
|
||||||
|
41 Viola
|
||||||
|
42 Cello
|
||||||
|
43 Contrabass
|
||||||
|
44 Tremolo Strings
|
||||||
|
45 Pizzicato Strings
|
||||||
|
46 Orchestral Strings
|
||||||
|
47 Timpani
|
||||||
|
48 String Ensemble 1
|
||||||
|
49 String Ensemble 2
|
||||||
|
50 SynthStrings 1
|
||||||
|
51 SynthStrings 2
|
||||||
|
52 Choir Aahs
|
||||||
|
53 Voice Oohs
|
||||||
|
54 Synth Voice
|
||||||
|
55 Orchestra Hit
|
||||||
|
56 Trumpet
|
||||||
|
57 Trombone
|
||||||
|
58 Tuba
|
||||||
|
59 Muted Trumpet
|
||||||
|
60 French Horn
|
||||||
|
61 Brass Section
|
||||||
|
62 SynthBrass 1
|
||||||
|
63 SynthBrass 2
|
||||||
|
64 Soprano Sax
|
||||||
|
65 Alto Sax
|
||||||
|
66 Tenor Sax
|
||||||
|
67 Baritone Sax
|
||||||
|
68 Oboe
|
||||||
|
69 English Horn
|
||||||
|
70 Bassoon
|
||||||
|
71 Clarinet
|
||||||
|
72 Piccolo
|
||||||
|
73 Flute
|
||||||
|
74 Recorder
|
||||||
|
75 Pan Flute
|
||||||
|
76 Blown Bottle
|
||||||
|
77 Shakuhachi
|
||||||
|
78 Whistle
|
||||||
|
79 Ocarina
|
||||||
|
80 Lead 1 (square)
|
||||||
|
81 Lead 2 (sawtooth)
|
||||||
|
82 Lead 3 (calliope)
|
||||||
|
83 Lead 4 (chiff)
|
||||||
|
84 Lead 5 (charang)
|
||||||
|
85 Lead 6 (voice)
|
||||||
|
86 Lead 7 (fifths)
|
||||||
|
87 Lead 8 (bass + lead)
|
||||||
|
88 Pad 1 (new age)
|
||||||
|
89 Pad 2 (warm)
|
||||||
|
90 Pad 3 (polysynth)
|
||||||
|
91 Pad 4 (choir)
|
||||||
|
92 Pad 5 (bowed)
|
||||||
|
93 Pad 6 (metallic)
|
||||||
|
94 Pad 7 (halo)
|
||||||
|
95 Pad 8 (sweep)
|
||||||
|
96 FX 1 (rain)
|
||||||
|
97 FX 2 (soundtrack)
|
||||||
|
98 FX 3 (crystal)
|
||||||
|
99 FX 4 (atmosphere)
|
||||||
|
100 FX 5 (brightness)
|
||||||
|
101 FX 6 (goblins)
|
||||||
|
102 FX 7 (echoes)
|
||||||
|
103 FX 8 (sci-fi)
|
||||||
|
104 Sitar
|
||||||
|
105 Banjo
|
||||||
|
106 Shamisen
|
||||||
|
107 Koto
|
||||||
|
108 Kalimba
|
||||||
|
109 Bagpipe
|
||||||
|
110 Fiddle
|
||||||
|
111 Shanai
|
||||||
|
112 Tinkle Bell
|
||||||
|
113 Agogo
|
||||||
|
114 Steel Drums
|
||||||
|
115 Woodblock
|
||||||
|
116 Taiko Drum
|
||||||
|
117 Melodic Tom
|
||||||
|
118 Synth Drum
|
||||||
|
119 Reverse Cymbal
|
||||||
|
120 Guitar Fret Noise
|
||||||
|
121 Breath Noise
|
||||||
|
122 Seashore
|
||||||
|
123 Bird Tweet
|
||||||
|
124 Telephone Ring
|
||||||
|
125 Helicopter
|
||||||
|
126 Applause
|
||||||
|
127 Gunshot
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIinstrument');
|
||||||
|
}
|
||||||
|
|
||||||
|
function GeneralMIDIpercussionLookup($instrumentid) {
|
||||||
|
|
||||||
|
$begin = __LINE__;
|
||||||
|
|
||||||
|
/** This is not a comment!
|
||||||
|
|
||||||
|
35 Acoustic Bass Drum
|
||||||
|
36 Bass Drum 1
|
||||||
|
37 Side Stick
|
||||||
|
38 Acoustic Snare
|
||||||
|
39 Hand Clap
|
||||||
|
40 Electric Snare
|
||||||
|
41 Low Floor Tom
|
||||||
|
42 Closed Hi-Hat
|
||||||
|
43 High Floor Tom
|
||||||
|
44 Pedal Hi-Hat
|
||||||
|
45 Low Tom
|
||||||
|
46 Open Hi-Hat
|
||||||
|
47 Low-Mid Tom
|
||||||
|
48 Hi-Mid Tom
|
||||||
|
49 Crash Cymbal 1
|
||||||
|
50 High Tom
|
||||||
|
51 Ride Cymbal 1
|
||||||
|
52 Chinese Cymbal
|
||||||
|
53 Ride Bell
|
||||||
|
54 Tambourine
|
||||||
|
55 Splash Cymbal
|
||||||
|
56 Cowbell
|
||||||
|
57 Crash Cymbal 2
|
||||||
|
59 Ride Cymbal 2
|
||||||
|
60 Hi Bongo
|
||||||
|
61 Low Bongo
|
||||||
|
62 Mute Hi Conga
|
||||||
|
63 Open Hi Conga
|
||||||
|
64 Low Conga
|
||||||
|
65 High Timbale
|
||||||
|
66 Low Timbale
|
||||||
|
67 High Agogo
|
||||||
|
68 Low Agogo
|
||||||
|
69 Cabasa
|
||||||
|
70 Maracas
|
||||||
|
71 Short Whistle
|
||||||
|
72 Long Whistle
|
||||||
|
73 Short Guiro
|
||||||
|
74 Long Guiro
|
||||||
|
75 Claves
|
||||||
|
76 Hi Wood Block
|
||||||
|
77 Low Wood Block
|
||||||
|
78 Mute Cuica
|
||||||
|
79 Open Cuica
|
||||||
|
80 Mute Triangle
|
||||||
|
81 Open Triangle
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIpercussion');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
101
app/library/getid3/module.audio.mod.php
Normal file
101
app/library/getid3/module.audio.mod.php
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.mod.php //
|
||||||
|
// module for analyzing MOD Audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_mod extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$fileheader = fread($this->getid3->fp, 1088);
|
||||||
|
if (preg_match('#^IMPM#', $fileheader)) {
|
||||||
|
return $this->getITheaderFilepointer();
|
||||||
|
} elseif (preg_match('#^Extended Module#', $fileheader)) {
|
||||||
|
return $this->getXMheaderFilepointer();
|
||||||
|
} elseif (preg_match('#^.{44}SCRM#', $fileheader)) {
|
||||||
|
return $this->getS3MheaderFilepointer();
|
||||||
|
} elseif (preg_match('#^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)#', $fileheader)) {
|
||||||
|
return $this->getMODheaderFilepointer();
|
||||||
|
}
|
||||||
|
$info['error'][] = 'This is not a known type of MOD file';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getMODheaderFilepointer() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'] + 1080);
|
||||||
|
$FormatID = fread($this->getid3->fp, 4);
|
||||||
|
if (!preg_match('#^(M.K.|[5-9]CHN|[1-3][0-9]CH)$#', $FormatID)) {
|
||||||
|
$info['error'][] = 'This is not a known type of MOD file';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['fileformat'] = 'mod';
|
||||||
|
|
||||||
|
$info['error'][] = 'MOD parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getXMheaderFilepointer() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset']);
|
||||||
|
$FormatID = fread($this->getid3->fp, 15);
|
||||||
|
if (!preg_match('#^Extended Module$#', $FormatID)) {
|
||||||
|
$info['error'][] = 'This is not a known type of XM-MOD file';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['fileformat'] = 'xm';
|
||||||
|
|
||||||
|
$info['error'][] = 'XM-MOD parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getS3MheaderFilepointer() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'] + 44);
|
||||||
|
$FormatID = fread($this->getid3->fp, 4);
|
||||||
|
if (!preg_match('#^SCRM$#', $FormatID)) {
|
||||||
|
$info['error'][] = 'This is not a ScreamTracker MOD file';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['fileformat'] = 's3m';
|
||||||
|
|
||||||
|
$info['error'][] = 'ScreamTracker parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getITheaderFilepointer() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset']);
|
||||||
|
$FormatID = fread($this->getid3->fp, 4);
|
||||||
|
if (!preg_match('#^IMPM$#', $FormatID)) {
|
||||||
|
$info['error'][] = 'This is not an ImpulseTracker MOD file';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['fileformat'] = 'it';
|
||||||
|
|
||||||
|
$info['error'][] = 'ImpulseTracker parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
205
app/library/getid3/module.audio.monkey.php
Normal file
205
app/library/getid3/module.audio.monkey.php
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.monkey.php //
|
||||||
|
// module for analyzing Monkey's Audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_monkey extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
// based loosely on code from TMonkey by Jurgen Faul <jfaulØgmx*de>
|
||||||
|
// http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html
|
||||||
|
|
||||||
|
$info['fileformat'] = 'mac';
|
||||||
|
$info['audio']['dataformat'] = 'mac';
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
$info['audio']['lossless'] = true;
|
||||||
|
|
||||||
|
$info['monkeys_audio']['raw'] = array();
|
||||||
|
$thisfile_monkeysaudio = &$info['monkeys_audio'];
|
||||||
|
$thisfile_monkeysaudio_raw = &$thisfile_monkeysaudio['raw'];
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$MACheaderData = fread($this->getid3->fp, 74);
|
||||||
|
|
||||||
|
$thisfile_monkeysaudio_raw['magic'] = substr($MACheaderData, 0, 4);
|
||||||
|
$magic = 'MAC ';
|
||||||
|
if ($thisfile_monkeysaudio_raw['magic'] != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_monkeysaudio_raw['magic']).'"';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$thisfile_monkeysaudio_raw['nVersion'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 4, 2)); // appears to be uint32 in 3.98+
|
||||||
|
|
||||||
|
if ($thisfile_monkeysaudio_raw['nVersion'] < 3980) {
|
||||||
|
$thisfile_monkeysaudio_raw['nCompressionLevel'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 6, 2));
|
||||||
|
$thisfile_monkeysaudio_raw['nFormatFlags'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 8, 2));
|
||||||
|
$thisfile_monkeysaudio_raw['nChannels'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 10, 2));
|
||||||
|
$thisfile_monkeysaudio_raw['nSampleRate'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 12, 4));
|
||||||
|
$thisfile_monkeysaudio_raw['nHeaderDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 16, 4));
|
||||||
|
$thisfile_monkeysaudio_raw['nWAVTerminatingBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 20, 4));
|
||||||
|
$thisfile_monkeysaudio_raw['nTotalFrames'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 24, 4));
|
||||||
|
$thisfile_monkeysaudio_raw['nFinalFrameSamples'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 28, 4));
|
||||||
|
$thisfile_monkeysaudio_raw['nPeakLevel'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 32, 4));
|
||||||
|
$thisfile_monkeysaudio_raw['nSeekElements'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 38, 2));
|
||||||
|
$offset = 8;
|
||||||
|
} else {
|
||||||
|
$offset = 8;
|
||||||
|
// APE_DESCRIPTOR
|
||||||
|
$thisfile_monkeysaudio_raw['nDescriptorBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_monkeysaudio_raw['nHeaderBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_monkeysaudio_raw['nSeekTableBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_monkeysaudio_raw['nHeaderDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_monkeysaudio_raw['nAPEFrameDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_monkeysaudio_raw['nAPEFrameDataBytesHigh'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_monkeysaudio_raw['nTerminatingDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_monkeysaudio_raw['cFileMD5'] = substr($MACheaderData, $offset, 16);
|
||||||
|
$offset += 16;
|
||||||
|
|
||||||
|
// APE_HEADER
|
||||||
|
$thisfile_monkeysaudio_raw['nCompressionLevel'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_monkeysaudio_raw['nFormatFlags'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_monkeysaudio_raw['nBlocksPerFrame'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_monkeysaudio_raw['nFinalFrameBlocks'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_monkeysaudio_raw['nTotalFrames'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_monkeysaudio_raw['nBitsPerSample'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_monkeysaudio_raw['nChannels'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_monkeysaudio_raw['nSampleRate'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_monkeysaudio['flags']['8-bit'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0001);
|
||||||
|
$thisfile_monkeysaudio['flags']['crc-32'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0002);
|
||||||
|
$thisfile_monkeysaudio['flags']['peak_level'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0004);
|
||||||
|
$thisfile_monkeysaudio['flags']['24-bit'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0008);
|
||||||
|
$thisfile_monkeysaudio['flags']['seek_elements'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0010);
|
||||||
|
$thisfile_monkeysaudio['flags']['no_wav_header'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0020);
|
||||||
|
$thisfile_monkeysaudio['version'] = $thisfile_monkeysaudio_raw['nVersion'] / 1000;
|
||||||
|
$thisfile_monkeysaudio['compression'] = $this->MonkeyCompressionLevelNameLookup($thisfile_monkeysaudio_raw['nCompressionLevel']);
|
||||||
|
if ($thisfile_monkeysaudio_raw['nVersion'] < 3980) {
|
||||||
|
$thisfile_monkeysaudio['samples_per_frame'] = $this->MonkeySamplesPerFrame($thisfile_monkeysaudio_raw['nVersion'], $thisfile_monkeysaudio_raw['nCompressionLevel']);
|
||||||
|
}
|
||||||
|
$thisfile_monkeysaudio['bits_per_sample'] = ($thisfile_monkeysaudio['flags']['24-bit'] ? 24 : ($thisfile_monkeysaudio['flags']['8-bit'] ? 8 : 16));
|
||||||
|
$thisfile_monkeysaudio['channels'] = $thisfile_monkeysaudio_raw['nChannels'];
|
||||||
|
$info['audio']['channels'] = $thisfile_monkeysaudio['channels'];
|
||||||
|
$thisfile_monkeysaudio['sample_rate'] = $thisfile_monkeysaudio_raw['nSampleRate'];
|
||||||
|
if ($thisfile_monkeysaudio['sample_rate'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt MAC file: frequency == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['audio']['sample_rate'] = $thisfile_monkeysaudio['sample_rate'];
|
||||||
|
if ($thisfile_monkeysaudio['flags']['peak_level']) {
|
||||||
|
$thisfile_monkeysaudio['peak_level'] = $thisfile_monkeysaudio_raw['nPeakLevel'];
|
||||||
|
$thisfile_monkeysaudio['peak_ratio'] = $thisfile_monkeysaudio['peak_level'] / pow(2, $thisfile_monkeysaudio['bits_per_sample'] - 1);
|
||||||
|
}
|
||||||
|
if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) {
|
||||||
|
$thisfile_monkeysaudio['samples'] = (($thisfile_monkeysaudio_raw['nTotalFrames'] - 1) * $thisfile_monkeysaudio_raw['nBlocksPerFrame']) + $thisfile_monkeysaudio_raw['nFinalFrameBlocks'];
|
||||||
|
} else {
|
||||||
|
$thisfile_monkeysaudio['samples'] = (($thisfile_monkeysaudio_raw['nTotalFrames'] - 1) * $thisfile_monkeysaudio['samples_per_frame']) + $thisfile_monkeysaudio_raw['nFinalFrameSamples'];
|
||||||
|
}
|
||||||
|
$thisfile_monkeysaudio['playtime'] = $thisfile_monkeysaudio['samples'] / $thisfile_monkeysaudio['sample_rate'];
|
||||||
|
if ($thisfile_monkeysaudio['playtime'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt MAC file: playtime == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['playtime_seconds'] = $thisfile_monkeysaudio['playtime'];
|
||||||
|
$thisfile_monkeysaudio['compressed_size'] = $info['avdataend'] - $info['avdataoffset'];
|
||||||
|
$thisfile_monkeysaudio['uncompressed_size'] = $thisfile_monkeysaudio['samples'] * $thisfile_monkeysaudio['channels'] * ($thisfile_monkeysaudio['bits_per_sample'] / 8);
|
||||||
|
if ($thisfile_monkeysaudio['uncompressed_size'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt MAC file: uncompressed_size == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$thisfile_monkeysaudio['compression_ratio'] = $thisfile_monkeysaudio['compressed_size'] / ($thisfile_monkeysaudio['uncompressed_size'] + $thisfile_monkeysaudio_raw['nHeaderDataBytes']);
|
||||||
|
$thisfile_monkeysaudio['bitrate'] = (($thisfile_monkeysaudio['samples'] * $thisfile_monkeysaudio['channels'] * $thisfile_monkeysaudio['bits_per_sample']) / $thisfile_monkeysaudio['playtime']) * $thisfile_monkeysaudio['compression_ratio'];
|
||||||
|
$info['audio']['bitrate'] = $thisfile_monkeysaudio['bitrate'];
|
||||||
|
|
||||||
|
// add size of MAC header to avdataoffset
|
||||||
|
if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) {
|
||||||
|
$info['avdataoffset'] += $thisfile_monkeysaudio_raw['nDescriptorBytes'];
|
||||||
|
$info['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderBytes'];
|
||||||
|
$info['avdataoffset'] += $thisfile_monkeysaudio_raw['nSeekTableBytes'];
|
||||||
|
$info['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderDataBytes'];
|
||||||
|
|
||||||
|
$info['avdataend'] -= $thisfile_monkeysaudio_raw['nTerminatingDataBytes'];
|
||||||
|
} else {
|
||||||
|
$info['avdataoffset'] += $offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) {
|
||||||
|
if ($thisfile_monkeysaudio_raw['cFileMD5'] === str_repeat("\x00", 16)) {
|
||||||
|
//$info['warning'][] = 'cFileMD5 is null';
|
||||||
|
} else {
|
||||||
|
$info['md5_data_source'] = '';
|
||||||
|
$md5 = $thisfile_monkeysaudio_raw['cFileMD5'];
|
||||||
|
for ($i = 0; $i < strlen($md5); $i++) {
|
||||||
|
$info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
|
||||||
|
unset($info['md5_data_source']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$info['audio']['bits_per_sample'] = $thisfile_monkeysaudio['bits_per_sample'];
|
||||||
|
$info['audio']['encoder'] = 'MAC v'.number_format($thisfile_monkeysaudio['version'], 2);
|
||||||
|
$info['audio']['encoder_options'] = ucfirst($thisfile_monkeysaudio['compression']).' compression';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function MonkeyCompressionLevelNameLookup($compressionlevel) {
|
||||||
|
static $MonkeyCompressionLevelNameLookup = array(
|
||||||
|
0 => 'unknown',
|
||||||
|
1000 => 'fast',
|
||||||
|
2000 => 'normal',
|
||||||
|
3000 => 'high',
|
||||||
|
4000 => 'extra-high',
|
||||||
|
5000 => 'insane'
|
||||||
|
);
|
||||||
|
return (isset($MonkeyCompressionLevelNameLookup[$compressionlevel]) ? $MonkeyCompressionLevelNameLookup[$compressionlevel] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function MonkeySamplesPerFrame($versionid, $compressionlevel) {
|
||||||
|
if ($versionid >= 3950) {
|
||||||
|
return 73728 * 4;
|
||||||
|
} elseif ($versionid >= 3900) {
|
||||||
|
return 73728;
|
||||||
|
} elseif (($versionid >= 3800) && ($compressionlevel == 4000)) {
|
||||||
|
return 73728;
|
||||||
|
} else {
|
||||||
|
return 9216;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
2011
app/library/getid3/module.audio.mp3.php
Normal file
2011
app/library/getid3/module.audio.mp3.php
Normal file
File diff suppressed because it is too large
Load diff
509
app/library/getid3/module.audio.mpc.php
Normal file
509
app/library/getid3/module.audio.mpc.php
Normal file
|
@ -0,0 +1,509 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.mpc.php //
|
||||||
|
// module for analyzing Musepack/MPEG+ Audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_mpc extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['mpc']['header'] = array();
|
||||||
|
$thisfile_mpc_header = &$info['mpc']['header'];
|
||||||
|
|
||||||
|
$info['fileformat'] = 'mpc';
|
||||||
|
$info['audio']['dataformat'] = 'mpc';
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
$info['audio']['channels'] = 2; // up to SV7 the format appears to have been hardcoded for stereo only
|
||||||
|
$info['audio']['lossless'] = false;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$MPCheaderData = fread($this->getid3->fp, 4);
|
||||||
|
$info['mpc']['header']['preamble'] = substr($MPCheaderData, 0, 4); // should be 'MPCK' (SV8) or 'MP+' (SV7), otherwise possible stream data (SV4-SV6)
|
||||||
|
if (preg_match('#^MPCK#', $info['mpc']['header']['preamble'])) {
|
||||||
|
|
||||||
|
// this is SV8
|
||||||
|
return $this->ParseMPCsv8();
|
||||||
|
|
||||||
|
} elseif (preg_match('#^MP\+#', $info['mpc']['header']['preamble'])) {
|
||||||
|
|
||||||
|
// this is SV7
|
||||||
|
return $this->ParseMPCsv7();
|
||||||
|
|
||||||
|
} elseif (preg_match('/^[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]/s', $MPCheaderData)) {
|
||||||
|
|
||||||
|
// this is SV4 - SV6, handle seperately
|
||||||
|
return $this->ParseMPCsv6();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['error'][] = 'Expecting "MP+" or "MPCK" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($MPCheaderData, 0, 4)).'"';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['mpc']);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ParseMPCsv8() {
|
||||||
|
// this is SV8
|
||||||
|
// http://trac.musepack.net/trac/wiki/SV8Specification
|
||||||
|
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
$thisfile_mpc_header = &$info['mpc']['header'];
|
||||||
|
|
||||||
|
$keyNameSize = 2;
|
||||||
|
$maxHandledPacketLength = 9; // specs say: "n*8; 0 < n < 10"
|
||||||
|
|
||||||
|
$offset = ftell($this->getid3->fp);
|
||||||
|
while ($offset < $info['avdataend']) {
|
||||||
|
$thisPacket = array();
|
||||||
|
$thisPacket['offset'] = $offset;
|
||||||
|
$packet_offset = 0;
|
||||||
|
|
||||||
|
// Size is a variable-size field, could be 1-4 bytes (possibly more?)
|
||||||
|
// read enough data in and figure out the exact size later
|
||||||
|
$MPCheaderData = fread($this->getid3->fp, $keyNameSize + $maxHandledPacketLength);
|
||||||
|
$packet_offset += $keyNameSize;
|
||||||
|
$thisPacket['key'] = substr($MPCheaderData, 0, $keyNameSize);
|
||||||
|
$thisPacket['key_name'] = $this->MPCsv8PacketName($thisPacket['key']);
|
||||||
|
if ($thisPacket['key'] == $thisPacket['key_name']) {
|
||||||
|
$info['error'][] = 'Found unexpected key value "'.$thisPacket['key'].'" at offset '.$thisPacket['offset'];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$packetLength = 0;
|
||||||
|
$thisPacket['packet_size'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $keyNameSize), $packetLength); // includes keyname and packet_size field
|
||||||
|
if ($thisPacket['packet_size'] === false) {
|
||||||
|
$info['error'][] = 'Did not find expected packet length within '.$maxHandledPacketLength.' bytes at offset '.($thisPacket['offset'] + $keyNameSize);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$packet_offset += $packetLength;
|
||||||
|
$offset += $thisPacket['packet_size'];
|
||||||
|
|
||||||
|
switch ($thisPacket['key']) {
|
||||||
|
case 'SH': // Stream Header
|
||||||
|
$moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength;
|
||||||
|
if ($moreBytesToRead > 0) {
|
||||||
|
$MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead);
|
||||||
|
}
|
||||||
|
$thisPacket['crc'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 4));
|
||||||
|
$packet_offset += 4;
|
||||||
|
$thisPacket['stream_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
|
||||||
|
$packet_offset += 1;
|
||||||
|
|
||||||
|
$packetLength = 0;
|
||||||
|
$thisPacket['sample_count'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength);
|
||||||
|
$packet_offset += $packetLength;
|
||||||
|
|
||||||
|
$packetLength = 0;
|
||||||
|
$thisPacket['beginning_silence'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength);
|
||||||
|
$packet_offset += $packetLength;
|
||||||
|
|
||||||
|
$otherUsefulData = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
|
||||||
|
$packet_offset += 2;
|
||||||
|
$thisPacket['sample_frequency_raw'] = (($otherUsefulData & 0xE000) >> 13);
|
||||||
|
$thisPacket['max_bands_used'] = (($otherUsefulData & 0x1F00) >> 8);
|
||||||
|
$thisPacket['channels'] = (($otherUsefulData & 0x00F0) >> 4) + 1;
|
||||||
|
$thisPacket['ms_used'] = (bool) (($otherUsefulData & 0x0008) >> 3);
|
||||||
|
$thisPacket['audio_block_frames'] = (($otherUsefulData & 0x0007) >> 0);
|
||||||
|
$thisPacket['sample_frequency'] = $this->MPCfrequencyLookup($thisPacket['sample_frequency_raw']);
|
||||||
|
|
||||||
|
$thisfile_mpc_header['mid_side_stereo'] = $thisPacket['ms_used'];
|
||||||
|
$thisfile_mpc_header['sample_rate'] = $thisPacket['sample_frequency'];
|
||||||
|
$thisfile_mpc_header['samples'] = $thisPacket['sample_count'];
|
||||||
|
$thisfile_mpc_header['stream_version_major'] = $thisPacket['stream_version'];
|
||||||
|
|
||||||
|
$info['audio']['channels'] = $thisPacket['channels'];
|
||||||
|
$info['audio']['sample_rate'] = $thisPacket['sample_frequency'];
|
||||||
|
$info['playtime_seconds'] = $thisPacket['sample_count'] / $thisPacket['sample_frequency'];
|
||||||
|
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'RG': // Replay Gain
|
||||||
|
$moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength;
|
||||||
|
if ($moreBytesToRead > 0) {
|
||||||
|
$MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead);
|
||||||
|
}
|
||||||
|
$thisPacket['replaygain_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
|
||||||
|
$packet_offset += 1;
|
||||||
|
$thisPacket['replaygain_title_gain'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
|
||||||
|
$packet_offset += 2;
|
||||||
|
$thisPacket['replaygain_title_peak'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
|
||||||
|
$packet_offset += 2;
|
||||||
|
$thisPacket['replaygain_album_gain'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
|
||||||
|
$packet_offset += 2;
|
||||||
|
$thisPacket['replaygain_album_peak'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
|
||||||
|
$packet_offset += 2;
|
||||||
|
|
||||||
|
if ($thisPacket['replaygain_title_gain']) { $info['replay_gain']['title']['gain'] = $thisPacket['replaygain_title_gain']; }
|
||||||
|
if ($thisPacket['replaygain_title_peak']) { $info['replay_gain']['title']['peak'] = $thisPacket['replaygain_title_peak']; }
|
||||||
|
if ($thisPacket['replaygain_album_gain']) { $info['replay_gain']['album']['gain'] = $thisPacket['replaygain_album_gain']; }
|
||||||
|
if ($thisPacket['replaygain_album_peak']) { $info['replay_gain']['album']['peak'] = $thisPacket['replaygain_album_peak']; }
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'EI': // Encoder Info
|
||||||
|
$moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength;
|
||||||
|
if ($moreBytesToRead > 0) {
|
||||||
|
$MPCheaderData .= fread($this->getid3->fp, $moreBytesToRead);
|
||||||
|
}
|
||||||
|
$profile_pns = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
|
||||||
|
$packet_offset += 1;
|
||||||
|
$quality_int = (($profile_pns & 0xF0) >> 4);
|
||||||
|
$quality_dec = (($profile_pns & 0x0E) >> 3);
|
||||||
|
$thisPacket['quality'] = (float) $quality_int + ($quality_dec / 8);
|
||||||
|
$thisPacket['pns_tool'] = (bool) (($profile_pns & 0x01) >> 0);
|
||||||
|
$thisPacket['version_major'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
|
||||||
|
$packet_offset += 1;
|
||||||
|
$thisPacket['version_minor'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
|
||||||
|
$packet_offset += 1;
|
||||||
|
$thisPacket['version_build'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
|
||||||
|
$packet_offset += 1;
|
||||||
|
$thisPacket['version'] = $thisPacket['version_major'].'.'.$thisPacket['version_minor'].'.'.$thisPacket['version_build'];
|
||||||
|
|
||||||
|
$info['audio']['encoder'] = 'MPC v'.$thisPacket['version'].' ('.(($thisPacket['version_minor'] % 2) ? 'unstable' : 'stable').')';
|
||||||
|
$thisfile_mpc_header['encoder_version'] = $info['audio']['encoder'];
|
||||||
|
//$thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] / 1.5875); // values can range from 0.000 to 15.875, mapped to qualities of 0.0 to 10.0
|
||||||
|
$thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] - 5); // values can range from 0.000 to 15.875, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'SO': // Seek Table Offset
|
||||||
|
$packetLength = 0;
|
||||||
|
$thisPacket['seek_table_offset'] = $thisPacket['offset'] + $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength);
|
||||||
|
$packet_offset += $packetLength;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ST': // Seek Table
|
||||||
|
case 'SE': // Stream End
|
||||||
|
case 'AP': // Audio Data
|
||||||
|
// nothing useful here, just skip this packet
|
||||||
|
$thisPacket = array();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['error'][] = 'Found unhandled key type "'.$thisPacket['key'].'" at offset '.$thisPacket['offset'];
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!empty($thisPacket)) {
|
||||||
|
$info['mpc']['packets'][] = $thisPacket;
|
||||||
|
}
|
||||||
|
fseek($this->getid3->fp, $offset);
|
||||||
|
}
|
||||||
|
$thisfile_mpc_header['size'] = $offset;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ParseMPCsv7() {
|
||||||
|
// this is SV7
|
||||||
|
// http://www.uni-jena.de/~pfk/mpp/sv8/header.html
|
||||||
|
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
$thisfile_mpc_header = &$info['mpc']['header'];
|
||||||
|
$offset = 0;
|
||||||
|
|
||||||
|
$thisfile_mpc_header['size'] = 28;
|
||||||
|
$MPCheaderData = $info['mpc']['header']['preamble'];
|
||||||
|
$MPCheaderData .= fread($this->getid3->fp, $thisfile_mpc_header['size'] - strlen($info['mpc']['header']['preamble']));
|
||||||
|
$offset = strlen('MP+');
|
||||||
|
|
||||||
|
$StreamVersionByte = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
$thisfile_mpc_header['stream_version_major'] = ($StreamVersionByte & 0x0F) >> 0;
|
||||||
|
$thisfile_mpc_header['stream_version_minor'] = ($StreamVersionByte & 0xF0) >> 4; // should always be 0, subversions no longer exist in SV8
|
||||||
|
$thisfile_mpc_header['frame_count'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
if ($thisfile_mpc_header['stream_version_major'] != 7) {
|
||||||
|
$info['error'][] = 'Only Musepack SV7 supported (this file claims to be v'.$thisfile_mpc_header['stream_version_major'].')';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$FlagsDWORD1 = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_mpc_header['intensity_stereo'] = (bool) (($FlagsDWORD1 & 0x80000000) >> 31);
|
||||||
|
$thisfile_mpc_header['mid_side_stereo'] = (bool) (($FlagsDWORD1 & 0x40000000) >> 30);
|
||||||
|
$thisfile_mpc_header['max_subband'] = ($FlagsDWORD1 & 0x3F000000) >> 24;
|
||||||
|
$thisfile_mpc_header['raw']['profile'] = ($FlagsDWORD1 & 0x00F00000) >> 20;
|
||||||
|
$thisfile_mpc_header['begin_loud'] = (bool) (($FlagsDWORD1 & 0x00080000) >> 19);
|
||||||
|
$thisfile_mpc_header['end_loud'] = (bool) (($FlagsDWORD1 & 0x00040000) >> 18);
|
||||||
|
$thisfile_mpc_header['raw']['sample_rate'] = ($FlagsDWORD1 & 0x00030000) >> 16;
|
||||||
|
$thisfile_mpc_header['max_level'] = ($FlagsDWORD1 & 0x0000FFFF);
|
||||||
|
|
||||||
|
$thisfile_mpc_header['raw']['title_peak'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_mpc_header['raw']['title_gain'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2), true);
|
||||||
|
$offset += 2;
|
||||||
|
|
||||||
|
$thisfile_mpc_header['raw']['album_peak'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_mpc_header['raw']['album_gain'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2), true);
|
||||||
|
$offset += 2;
|
||||||
|
|
||||||
|
$FlagsDWORD2 = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_mpc_header['true_gapless'] = (bool) (($FlagsDWORD2 & 0x80000000) >> 31);
|
||||||
|
$thisfile_mpc_header['last_frame_length'] = ($FlagsDWORD2 & 0x7FF00000) >> 20;
|
||||||
|
|
||||||
|
|
||||||
|
$thisfile_mpc_header['raw']['not_sure_what'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 3));
|
||||||
|
$offset += 3;
|
||||||
|
$thisfile_mpc_header['raw']['encoder_version'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
|
||||||
|
$thisfile_mpc_header['profile'] = $this->MPCprofileNameLookup($thisfile_mpc_header['raw']['profile']);
|
||||||
|
$thisfile_mpc_header['sample_rate'] = $this->MPCfrequencyLookup($thisfile_mpc_header['raw']['sample_rate']);
|
||||||
|
if ($thisfile_mpc_header['sample_rate'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt MPC file: frequency == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate'];
|
||||||
|
$thisfile_mpc_header['samples'] = ((($thisfile_mpc_header['frame_count'] - 1) * 1152) + $thisfile_mpc_header['last_frame_length']) * $info['audio']['channels'];
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = ($thisfile_mpc_header['samples'] / $info['audio']['channels']) / $info['audio']['sample_rate'];
|
||||||
|
if ($info['playtime_seconds'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt MPC file: playtime_seconds == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add size of file header to avdataoffset - calc bitrate correctly + MD5 data
|
||||||
|
$info['avdataoffset'] += $thisfile_mpc_header['size'];
|
||||||
|
|
||||||
|
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
|
||||||
|
$thisfile_mpc_header['title_peak'] = $thisfile_mpc_header['raw']['title_peak'];
|
||||||
|
$thisfile_mpc_header['title_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['title_peak']);
|
||||||
|
if ($thisfile_mpc_header['raw']['title_gain'] < 0) {
|
||||||
|
$thisfile_mpc_header['title_gain_db'] = (float) (32768 + $thisfile_mpc_header['raw']['title_gain']) / -100;
|
||||||
|
} else {
|
||||||
|
$thisfile_mpc_header['title_gain_db'] = (float) $thisfile_mpc_header['raw']['title_gain'] / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_mpc_header['album_peak'] = $thisfile_mpc_header['raw']['album_peak'];
|
||||||
|
$thisfile_mpc_header['album_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['album_peak']);
|
||||||
|
if ($thisfile_mpc_header['raw']['album_gain'] < 0) {
|
||||||
|
$thisfile_mpc_header['album_gain_db'] = (float) (32768 + $thisfile_mpc_header['raw']['album_gain']) / -100;
|
||||||
|
} else {
|
||||||
|
$thisfile_mpc_header['album_gain_db'] = (float) $thisfile_mpc_header['raw']['album_gain'] / 100;;
|
||||||
|
}
|
||||||
|
$thisfile_mpc_header['encoder_version'] = $this->MPCencoderVersionLookup($thisfile_mpc_header['raw']['encoder_version']);
|
||||||
|
|
||||||
|
$info['replay_gain']['track']['adjustment'] = $thisfile_mpc_header['title_gain_db'];
|
||||||
|
$info['replay_gain']['album']['adjustment'] = $thisfile_mpc_header['album_gain_db'];
|
||||||
|
|
||||||
|
if ($thisfile_mpc_header['title_peak'] > 0) {
|
||||||
|
$info['replay_gain']['track']['peak'] = $thisfile_mpc_header['title_peak'];
|
||||||
|
} elseif (round($thisfile_mpc_header['max_level'] * 1.18) > 0) {
|
||||||
|
$info['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round($thisfile_mpc_header['max_level'] * 1.18)); // why? I don't know - see mppdec.c
|
||||||
|
}
|
||||||
|
if ($thisfile_mpc_header['album_peak'] > 0) {
|
||||||
|
$info['replay_gain']['album']['peak'] = $thisfile_mpc_header['album_peak'];
|
||||||
|
}
|
||||||
|
|
||||||
|
//$info['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major'].'.'.$thisfile_mpc_header['stream_version_minor'].', '.$thisfile_mpc_header['encoder_version'];
|
||||||
|
$info['audio']['encoder'] = $thisfile_mpc_header['encoder_version'];
|
||||||
|
$info['audio']['encoder_options'] = $thisfile_mpc_header['profile'];
|
||||||
|
$thisfile_mpc_header['quality'] = (float) ($thisfile_mpc_header['raw']['profile'] - 5); // values can range from 0 to 15, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ParseMPCsv6() {
|
||||||
|
// this is SV4 - SV6
|
||||||
|
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
$thisfile_mpc_header = &$info['mpc']['header'];
|
||||||
|
$offset = 0;
|
||||||
|
|
||||||
|
$thisfile_mpc_header['size'] = 8;
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$MPCheaderData = fread($this->getid3->fp, $thisfile_mpc_header['size']);
|
||||||
|
|
||||||
|
// add size of file header to avdataoffset - calc bitrate correctly + MD5 data
|
||||||
|
$info['avdataoffset'] += $thisfile_mpc_header['size'];
|
||||||
|
|
||||||
|
// Most of this code adapted from Jurgen Faul's MPEGplus source code - thanks Jurgen! :)
|
||||||
|
$HeaderDWORD[0] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 0, 4));
|
||||||
|
$HeaderDWORD[1] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 4, 4));
|
||||||
|
|
||||||
|
|
||||||
|
// DDDD DDDD CCCC CCCC BBBB BBBB AAAA AAAA
|
||||||
|
// aaaa aaaa abcd dddd dddd deee eeff ffff
|
||||||
|
//
|
||||||
|
// a = bitrate = anything
|
||||||
|
// b = IS = anything
|
||||||
|
// c = MS = anything
|
||||||
|
// d = streamversion = 0000000004 or 0000000005 or 0000000006
|
||||||
|
// e = maxband = anything
|
||||||
|
// f = blocksize = 000001 for SV5+, anything(?) for SV4
|
||||||
|
|
||||||
|
$thisfile_mpc_header['target_bitrate'] = (($HeaderDWORD[0] & 0xFF800000) >> 23);
|
||||||
|
$thisfile_mpc_header['intensity_stereo'] = (bool) (($HeaderDWORD[0] & 0x00400000) >> 22);
|
||||||
|
$thisfile_mpc_header['mid_side_stereo'] = (bool) (($HeaderDWORD[0] & 0x00200000) >> 21);
|
||||||
|
$thisfile_mpc_header['stream_version_major'] = ($HeaderDWORD[0] & 0x001FF800) >> 11;
|
||||||
|
$thisfile_mpc_header['stream_version_minor'] = 0; // no sub-version numbers before SV7
|
||||||
|
$thisfile_mpc_header['max_band'] = ($HeaderDWORD[0] & 0x000007C0) >> 6; // related to lowpass frequency, not sure how it translates exactly
|
||||||
|
$thisfile_mpc_header['block_size'] = ($HeaderDWORD[0] & 0x0000003F);
|
||||||
|
|
||||||
|
switch ($thisfile_mpc_header['stream_version_major']) {
|
||||||
|
case 4:
|
||||||
|
$thisfile_mpc_header['frame_count'] = ($HeaderDWORD[1] >> 16);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
$thisfile_mpc_header['frame_count'] = $HeaderDWORD[1];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['error'] = 'Expecting 4, 5 or 6 in version field, found '.$thisfile_mpc_header['stream_version_major'].' instead';
|
||||||
|
unset($info['mpc']);
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($thisfile_mpc_header['stream_version_major'] > 4) && ($thisfile_mpc_header['block_size'] != 1)) {
|
||||||
|
$info['warning'][] = 'Block size expected to be 1, actual value found: '.$thisfile_mpc_header['block_size'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_mpc_header['sample_rate'] = 44100; // AB: used by all files up to SV7
|
||||||
|
$info['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate'];
|
||||||
|
$thisfile_mpc_header['samples'] = $thisfile_mpc_header['frame_count'] * 1152 * $info['audio']['channels'];
|
||||||
|
|
||||||
|
if ($thisfile_mpc_header['target_bitrate'] == 0) {
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
} else {
|
||||||
|
$info['audio']['bitrate_mode'] = 'cbr';
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['mpc']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 * 44100 / $thisfile_mpc_header['frame_count'] / 1152;
|
||||||
|
$info['audio']['bitrate'] = $info['mpc']['bitrate'];
|
||||||
|
$info['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major'];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function MPCprofileNameLookup($profileid) {
|
||||||
|
static $MPCprofileNameLookup = array(
|
||||||
|
0 => 'no profile',
|
||||||
|
1 => 'Experimental',
|
||||||
|
2 => 'unused',
|
||||||
|
3 => 'unused',
|
||||||
|
4 => 'unused',
|
||||||
|
5 => 'below Telephone (q = 0.0)',
|
||||||
|
6 => 'below Telephone (q = 1.0)',
|
||||||
|
7 => 'Telephone (q = 2.0)',
|
||||||
|
8 => 'Thumb (q = 3.0)',
|
||||||
|
9 => 'Radio (q = 4.0)',
|
||||||
|
10 => 'Standard (q = 5.0)',
|
||||||
|
11 => 'Extreme (q = 6.0)',
|
||||||
|
12 => 'Insane (q = 7.0)',
|
||||||
|
13 => 'BrainDead (q = 8.0)',
|
||||||
|
14 => 'above BrainDead (q = 9.0)',
|
||||||
|
15 => 'above BrainDead (q = 10.0)'
|
||||||
|
);
|
||||||
|
return (isset($MPCprofileNameLookup[$profileid]) ? $MPCprofileNameLookup[$profileid] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function MPCfrequencyLookup($frequencyid) {
|
||||||
|
static $MPCfrequencyLookup = array(
|
||||||
|
0 => 44100,
|
||||||
|
1 => 48000,
|
||||||
|
2 => 37800,
|
||||||
|
3 => 32000
|
||||||
|
);
|
||||||
|
return (isset($MPCfrequencyLookup[$frequencyid]) ? $MPCfrequencyLookup[$frequencyid] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function MPCpeakDBLookup($intvalue) {
|
||||||
|
if ($intvalue > 0) {
|
||||||
|
return ((log10($intvalue) / log10(2)) - 15) * 6;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function MPCencoderVersionLookup($encoderversion) {
|
||||||
|
//Encoder version * 100 (106 = 1.06)
|
||||||
|
//EncoderVersion % 10 == 0 Release (1.0)
|
||||||
|
//EncoderVersion % 2 == 0 Beta (1.06)
|
||||||
|
//EncoderVersion % 2 == 1 Alpha (1.05a...z)
|
||||||
|
|
||||||
|
if ($encoderversion == 0) {
|
||||||
|
// very old version, not known exactly which
|
||||||
|
return 'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($encoderversion % 10) == 0) {
|
||||||
|
|
||||||
|
// release version
|
||||||
|
return number_format($encoderversion / 100, 2);
|
||||||
|
|
||||||
|
} elseif (($encoderversion % 2) == 0) {
|
||||||
|
|
||||||
|
// beta version
|
||||||
|
return number_format($encoderversion / 100, 2).' beta';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// alpha version
|
||||||
|
return number_format($encoderversion / 100, 2).' alpha';
|
||||||
|
}
|
||||||
|
|
||||||
|
function SV8variableLengthInteger($data, &$packetLength, $maxHandledPacketLength=9) {
|
||||||
|
$packet_size = 0;
|
||||||
|
for ($packetLength = 1; $packetLength <= $maxHandledPacketLength; $packetLength++) {
|
||||||
|
// variable-length size field:
|
||||||
|
// bits, big-endian
|
||||||
|
// 0xxx xxxx - value 0 to 2^7-1
|
||||||
|
// 1xxx xxxx 0xxx xxxx - value 0 to 2^14-1
|
||||||
|
// 1xxx xxxx 1xxx xxxx 0xxx xxxx - value 0 to 2^21-1
|
||||||
|
// 1xxx xxxx 1xxx xxxx 1xxx xxxx 0xxx xxxx - value 0 to 2^28-1
|
||||||
|
// ...
|
||||||
|
$thisbyte = ord(substr($data, ($packetLength - 1), 1));
|
||||||
|
// look through bytes until find a byte with MSB==0
|
||||||
|
$packet_size = ($packet_size << 7);
|
||||||
|
$packet_size = ($packet_size | ($thisbyte & 0x7F));
|
||||||
|
if (($thisbyte & 0x80) === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($packetLength >= $maxHandledPacketLength) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $packet_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
function MPCsv8PacketName($packetKey) {
|
||||||
|
static $MPCsv8PacketName = array();
|
||||||
|
if (empty($MPCsv8PacketName)) {
|
||||||
|
$MPCsv8PacketName = array(
|
||||||
|
'AP' => 'Audio Packet',
|
||||||
|
'CT' => 'Chapter Tag',
|
||||||
|
'EI' => 'Encoder Info',
|
||||||
|
'RG' => 'Replay Gain',
|
||||||
|
'SE' => 'Stream End',
|
||||||
|
'SH' => 'Stream Header',
|
||||||
|
'SO' => 'Seek Table Offset',
|
||||||
|
'ST' => 'Seek Table',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (isset($MPCsv8PacketName[$packetKey]) ? $MPCsv8PacketName[$packetKey] : $packetKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
705
app/library/getid3/module.audio.ogg.php
Normal file
705
app/library/getid3/module.audio.ogg.php
Normal file
|
@ -0,0 +1,705 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.ogg.php //
|
||||||
|
// module for analyzing Ogg Vorbis, OggFLAC and Speex files //
|
||||||
|
// dependencies: module.audio.flac.php //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
|
||||||
|
|
||||||
|
class getid3_ogg extends getid3_handler
|
||||||
|
{
|
||||||
|
var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'ogg';
|
||||||
|
|
||||||
|
// Warn about illegal tags - only vorbiscomments are allowed
|
||||||
|
if (isset($info['id3v2'])) {
|
||||||
|
$info['warning'][] = 'Illegal ID3v2 tag present.';
|
||||||
|
}
|
||||||
|
if (isset($info['id3v1'])) {
|
||||||
|
$info['warning'][] = 'Illegal ID3v1 tag present.';
|
||||||
|
}
|
||||||
|
if (isset($info['ape'])) {
|
||||||
|
$info['warning'][] = 'Illegal APE tag present.';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Page 1 - Stream Header
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
|
||||||
|
$oggpageinfo = $this->ParseOggPageHeader();
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
|
||||||
|
|
||||||
|
if (ftell($this->getid3->fp) >= $this->getid3->fread_buffer_size()) {
|
||||||
|
$info['error'][] = 'Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['ogg']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filedata = fread($this->getid3->fp, $oggpageinfo['page_length']);
|
||||||
|
$filedataoffset = 0;
|
||||||
|
|
||||||
|
if (substr($filedata, 0, 4) == 'fLaC') {
|
||||||
|
|
||||||
|
$info['audio']['dataformat'] = 'flac';
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
$info['audio']['lossless'] = true;
|
||||||
|
|
||||||
|
} elseif (substr($filedata, 1, 6) == 'vorbis') {
|
||||||
|
|
||||||
|
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
|
||||||
|
|
||||||
|
} elseif (substr($filedata, 0, 8) == 'Speex ') {
|
||||||
|
|
||||||
|
// http://www.speex.org/manual/node10.html
|
||||||
|
|
||||||
|
$info['audio']['dataformat'] = 'speex';
|
||||||
|
$info['mime_type'] = 'audio/speex';
|
||||||
|
$info['audio']['bitrate_mode'] = 'abr';
|
||||||
|
$info['audio']['lossless'] = false;
|
||||||
|
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex '
|
||||||
|
$filedataoffset += 8;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20);
|
||||||
|
$filedataoffset += 20;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
|
||||||
|
$info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
|
||||||
|
$info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
|
||||||
|
$info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
|
||||||
|
$info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
|
||||||
|
$info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
|
||||||
|
|
||||||
|
$info['audio']['sample_rate'] = $info['speex']['sample_rate'];
|
||||||
|
$info['audio']['channels'] = $info['speex']['channels'];
|
||||||
|
if ($info['speex']['vbr']) {
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} elseif (substr($filedata, 0, 8) == "fishead\x00") {
|
||||||
|
|
||||||
|
// Ogg Skeleton version 3.0 Format Specification
|
||||||
|
// http://xiph.org/ogg/doc/skeleton.html
|
||||||
|
$filedataoffset += 8;
|
||||||
|
$info['ogg']['skeleton']['fishead']['raw']['version_major'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
|
||||||
|
$filedataoffset += 2;
|
||||||
|
$info['ogg']['skeleton']['fishead']['raw']['version_minor'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
|
||||||
|
$filedataoffset += 2;
|
||||||
|
$info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||||
|
$filedataoffset += 8;
|
||||||
|
$info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||||
|
$filedataoffset += 8;
|
||||||
|
$info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||||
|
$filedataoffset += 8;
|
||||||
|
$info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||||
|
$filedataoffset += 8;
|
||||||
|
$info['ogg']['skeleton']['fishead']['raw']['utc'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
|
||||||
|
$filedataoffset += 20;
|
||||||
|
|
||||||
|
$info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
|
||||||
|
$info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
|
||||||
|
$info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
|
||||||
|
$info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc'];
|
||||||
|
|
||||||
|
|
||||||
|
$counter = 0;
|
||||||
|
do {
|
||||||
|
$oggpageinfo = $this->ParseOggPageHeader();
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
|
||||||
|
$filedata = fread($this->getid3->fp, $oggpageinfo['page_length']);
|
||||||
|
fseek($this->getid3->fp, $oggpageinfo['page_end_offset'], SEEK_SET);
|
||||||
|
|
||||||
|
if (substr($filedata, 0, 8) == "fisbone\x00") {
|
||||||
|
|
||||||
|
$filedataoffset = 8;
|
||||||
|
$info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||||
|
$filedataoffset += 8;
|
||||||
|
$info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||||
|
$filedataoffset += 8;
|
||||||
|
$info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||||
|
$filedataoffset += 8;
|
||||||
|
$info['ogg']['skeleton']['fisbone']['raw']['preroll'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||||
|
$filedataoffset += 1;
|
||||||
|
$info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3);
|
||||||
|
$filedataoffset += 3;
|
||||||
|
|
||||||
|
} elseif (substr($filedata, 1, 6) == 'theora') {
|
||||||
|
|
||||||
|
$info['video']['dataformat'] = 'theora';
|
||||||
|
$info['error'][] = 'Ogg Theora not correctly handled in this version of getID3 ['.$this->getid3->version().']';
|
||||||
|
//break;
|
||||||
|
|
||||||
|
} elseif (substr($filedata, 1, 6) == 'vorbis') {
|
||||||
|
|
||||||
|
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$info['error'][] = 'unexpected';
|
||||||
|
//break;
|
||||||
|
}
|
||||||
|
//} while ($oggpageinfo['page_seqno'] == 0);
|
||||||
|
} while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
|
||||||
|
fseek($this->getid3->fp, $oggpageinfo['page_start_offset'], SEEK_SET);
|
||||||
|
|
||||||
|
|
||||||
|
$info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']';
|
||||||
|
//return false;
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"';
|
||||||
|
unset($info['ogg']);
|
||||||
|
unset($info['mime_type']);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page 2 - Comment Header
|
||||||
|
$oggpageinfo = $this->ParseOggPageHeader();
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
|
||||||
|
|
||||||
|
switch ($info['audio']['dataformat']) {
|
||||||
|
case 'vorbis':
|
||||||
|
$filedata = fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis'
|
||||||
|
|
||||||
|
$this->ParseVorbisCommentsFilepointer();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'flac':
|
||||||
|
$getid3_flac = new getid3_flac($this->getid3);
|
||||||
|
if (!$getid3_flac->FLACparseMETAdata()) {
|
||||||
|
$info['error'][] = 'Failed to parse FLAC headers';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
unset($getid3_flac);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'speex':
|
||||||
|
fseek($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
|
||||||
|
$this->ParseVorbisCommentsFilepointer();
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Last Page - Number of Samples
|
||||||
|
|
||||||
|
if (!getid3_lib::intValueSupported($info['avdataend'])) {
|
||||||
|
|
||||||
|
$info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0), SEEK_SET);
|
||||||
|
$LastChunkOfOgg = strrev(fread($this->getid3->fp, $this->getid3->fread_buffer_size()));
|
||||||
|
if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
|
||||||
|
fseek($this->getid3->fp, $info['avdataend'] - ($LastOggSpostion + strlen('SggO')), SEEK_SET);
|
||||||
|
$info['avdataend'] = ftell($this->getid3->fp);
|
||||||
|
$info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
|
||||||
|
$info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
|
||||||
|
if ($info['ogg']['samples'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!empty($info['audio']['sample_rate'])) {
|
||||||
|
$info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($info['ogg']['bitrate_average'])) {
|
||||||
|
$info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
|
||||||
|
} elseif (!empty($info['ogg']['bitrate_nominal'])) {
|
||||||
|
$info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
|
||||||
|
} elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
|
||||||
|
$info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
|
||||||
|
}
|
||||||
|
if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
|
||||||
|
if ($info['audio']['bitrate'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($info['ogg']['vendor'])) {
|
||||||
|
$info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
|
||||||
|
|
||||||
|
// Vorbis only
|
||||||
|
if ($info['audio']['dataformat'] == 'vorbis') {
|
||||||
|
|
||||||
|
// Vorbis 1.0 starts with Xiph.Org
|
||||||
|
if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
|
||||||
|
|
||||||
|
if ($info['audio']['bitrate_mode'] == 'abr') {
|
||||||
|
|
||||||
|
// Set -b 128 on abr files
|
||||||
|
$info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
|
||||||
|
|
||||||
|
} elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
|
||||||
|
// Set -q N on vbr files
|
||||||
|
$info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
|
||||||
|
$info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
$info['audio']['dataformat'] = 'vorbis';
|
||||||
|
$info['audio']['lossless'] = false;
|
||||||
|
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||||
|
$filedataoffset += 1;
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
|
||||||
|
$filedataoffset += 6;
|
||||||
|
$info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||||
|
$filedataoffset += 1;
|
||||||
|
$info['audio']['channels'] = $info['ogg']['numberofchannels'];
|
||||||
|
$info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
if ($info['ogg']['samplerate'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt Ogg file: sample rate == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['audio']['sample_rate'] = $info['ogg']['samplerate'];
|
||||||
|
$info['ogg']['samples'] = 0; // filled in later
|
||||||
|
$info['ogg']['bitrate_average'] = 0; // filled in later
|
||||||
|
$info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
|
||||||
|
$info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
|
||||||
|
$info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
|
||||||
|
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
|
||||||
|
if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
|
||||||
|
unset($info['ogg']['bitrate_max']);
|
||||||
|
$info['audio']['bitrate_mode'] = 'abr';
|
||||||
|
}
|
||||||
|
if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
|
||||||
|
unset($info['ogg']['bitrate_nominal']);
|
||||||
|
}
|
||||||
|
if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
|
||||||
|
unset($info['ogg']['bitrate_min']);
|
||||||
|
$info['audio']['bitrate_mode'] = 'abr';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ParseOggPageHeader() {
|
||||||
|
// http://xiph.org/ogg/vorbis/doc/framing.html
|
||||||
|
$oggheader['page_start_offset'] = ftell($this->getid3->fp); // where we started from in the file
|
||||||
|
|
||||||
|
$filedata = fread($this->getid3->fp, $this->getid3->fread_buffer_size());
|
||||||
|
$filedataoffset = 0;
|
||||||
|
while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
|
||||||
|
if ((ftell($this->getid3->fp) - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
|
||||||
|
// should be found before here
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
|
||||||
|
if (feof($this->getid3->fp) || (($filedata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size())) === false)) {
|
||||||
|
// get some more data, unless eof, in which case fail
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
|
||||||
|
|
||||||
|
$oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||||
|
$filedataoffset += 1;
|
||||||
|
$oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||||
|
$filedataoffset += 1;
|
||||||
|
$oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
|
||||||
|
$oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
|
||||||
|
$oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
|
||||||
|
|
||||||
|
$oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||||
|
$filedataoffset += 8;
|
||||||
|
$oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||||
|
$filedataoffset += 4;
|
||||||
|
$oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||||
|
$filedataoffset += 1;
|
||||||
|
$oggheader['page_length'] = 0;
|
||||||
|
for ($i = 0; $i < $oggheader['page_segments']; $i++) {
|
||||||
|
$oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||||
|
$filedataoffset += 1;
|
||||||
|
$oggheader['page_length'] += $oggheader['segment_table'][$i];
|
||||||
|
}
|
||||||
|
$oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
|
||||||
|
$oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length'];
|
||||||
|
fseek($this->getid3->fp, $oggheader['header_end_offset'], SEEK_SET);
|
||||||
|
|
||||||
|
return $oggheader;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ParseVorbisCommentsFilepointer() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$OriginalOffset = ftell($this->getid3->fp);
|
||||||
|
$commentdataoffset = 0;
|
||||||
|
$VorbisCommentPage = 1;
|
||||||
|
|
||||||
|
switch ($info['audio']['dataformat']) {
|
||||||
|
case 'vorbis':
|
||||||
|
$CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block
|
||||||
|
fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET);
|
||||||
|
$commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
|
||||||
|
$commentdata = fread($this->getid3->fp, getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
|
||||||
|
|
||||||
|
$commentdataoffset += (strlen('vorbis') + 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'flac':
|
||||||
|
$CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
|
||||||
|
fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET);
|
||||||
|
$commentdata = fread($this->getid3->fp, $info['flac']['VORBIS_COMMENT']['raw']['block_length']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'speex':
|
||||||
|
$CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block
|
||||||
|
fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET);
|
||||||
|
$commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
|
||||||
|
$commentdata = fread($this->getid3->fp, getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
|
||||||
|
$commentdataoffset += 4;
|
||||||
|
|
||||||
|
$info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
|
||||||
|
$commentdataoffset += $VendorSize;
|
||||||
|
|
||||||
|
$CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
|
||||||
|
$commentdataoffset += 4;
|
||||||
|
$info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
|
||||||
|
|
||||||
|
$basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
|
||||||
|
$ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
|
||||||
|
for ($i = 0; $i < $CommentsCount; $i++) {
|
||||||
|
|
||||||
|
$ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
|
||||||
|
|
||||||
|
if (ftell($this->getid3->fp) < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
|
||||||
|
if ($oggpageinfo = $this->ParseOggPageHeader()) {
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
|
||||||
|
|
||||||
|
$VorbisCommentPage++;
|
||||||
|
|
||||||
|
// First, save what we haven't read yet
|
||||||
|
$AsYetUnusedData = substr($commentdata, $commentdataoffset);
|
||||||
|
|
||||||
|
// Then take that data off the end
|
||||||
|
$commentdata = substr($commentdata, 0, $commentdataoffset);
|
||||||
|
|
||||||
|
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
|
||||||
|
$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
|
||||||
|
$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
|
||||||
|
|
||||||
|
// Finally, stick the unused data back on the end
|
||||||
|
$commentdata .= $AsYetUnusedData;
|
||||||
|
|
||||||
|
//$commentdata .= fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
|
||||||
|
$commentdata .= fread($this->getid3->fp, $this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
$ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
|
||||||
|
|
||||||
|
// replace avdataoffset with position just after the last vorbiscomment
|
||||||
|
$info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
|
||||||
|
|
||||||
|
$commentdataoffset += 4;
|
||||||
|
while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
|
||||||
|
if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
|
||||||
|
$info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments';
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$VorbisCommentPage++;
|
||||||
|
|
||||||
|
$oggpageinfo = $this->ParseOggPageHeader();
|
||||||
|
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
|
||||||
|
|
||||||
|
// First, save what we haven't read yet
|
||||||
|
$AsYetUnusedData = substr($commentdata, $commentdataoffset);
|
||||||
|
|
||||||
|
// Then take that data off the end
|
||||||
|
$commentdata = substr($commentdata, 0, $commentdataoffset);
|
||||||
|
|
||||||
|
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
|
||||||
|
$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
|
||||||
|
$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
|
||||||
|
|
||||||
|
// Finally, stick the unused data back on the end
|
||||||
|
$commentdata .= $AsYetUnusedData;
|
||||||
|
|
||||||
|
//$commentdata .= fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
|
||||||
|
if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
|
||||||
|
$info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($this->getid3->fp);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$readlength = getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
|
||||||
|
if ($readlength <= 0) {
|
||||||
|
$info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($this->getid3->fp);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$commentdata .= fread($this->getid3->fp, $readlength);
|
||||||
|
|
||||||
|
//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
|
||||||
|
}
|
||||||
|
$ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
|
||||||
|
$commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
|
||||||
|
$commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
|
||||||
|
|
||||||
|
if (!$commentstring) {
|
||||||
|
|
||||||
|
// no comment?
|
||||||
|
$info['warning'][] = 'Blank Ogg comment ['.$i.']';
|
||||||
|
|
||||||
|
} elseif (strstr($commentstring, '=')) {
|
||||||
|
|
||||||
|
$commentexploded = explode('=', $commentstring, 2);
|
||||||
|
$ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]);
|
||||||
|
$ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
|
||||||
|
$ThisFileInfo_ogg_comments_raw[$i]['data'] = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
|
||||||
|
$ThisFileInfo_ogg_comments_raw[$i]['data_length'] = strlen($ThisFileInfo_ogg_comments_raw[$i]['data']);
|
||||||
|
|
||||||
|
if (preg_match('#^(BM|GIF|\xFF\xD8\xFF|\x89\x50\x4E\x47\x0D\x0A\x1A\x0A|II\x2A\x00|MM\x00\x2A)#s', $ThisFileInfo_ogg_comments_raw[$i]['data'])) {
|
||||||
|
$imageinfo = array();
|
||||||
|
$imagechunkcheck = getid3_lib::GetDataImageSize($ThisFileInfo_ogg_comments_raw[$i]['data'], $imageinfo);
|
||||||
|
unset($imageinfo);
|
||||||
|
if (!empty($imagechunkcheck)) {
|
||||||
|
$ThisFileInfo_ogg_comments_raw[$i]['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
|
||||||
|
if ($ThisFileInfo_ogg_comments_raw[$i]['image_mime'] && ($ThisFileInfo_ogg_comments_raw[$i]['image_mime'] != 'application/octet-stream')) {
|
||||||
|
unset($ThisFileInfo_ogg_comments_raw[$i]['value']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($ThisFileInfo_ogg_comments_raw[$i]['value'])) {
|
||||||
|
unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
|
||||||
|
$info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
if ($this->inline_attachments === false) {
|
||||||
|
// skip entirely
|
||||||
|
unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($this->inline_attachments === true) {
|
||||||
|
// great
|
||||||
|
} elseif (is_int($this->inline_attachments)) {
|
||||||
|
if ($this->inline_attachments < $ThisFileInfo_ogg_comments_raw[$i]['data_length']) {
|
||||||
|
// too big, skip
|
||||||
|
$info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' is too large to process inline ('.number_format($ThisFileInfo_ogg_comments_raw[$i]['data_length']).' bytes)';
|
||||||
|
unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} elseif (is_string($this->inline_attachments)) {
|
||||||
|
$this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
|
||||||
|
if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
|
||||||
|
// cannot write, skip
|
||||||
|
$info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
|
||||||
|
unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we get this far, must be OK
|
||||||
|
if (is_string($this->inline_attachments)) {
|
||||||
|
$destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$ThisFileInfo_ogg_comments_raw[$i]['offset'];
|
||||||
|
if (!file_exists($destination_filename) || is_writable($destination_filename)) {
|
||||||
|
file_put_contents($destination_filename, $ThisFileInfo_ogg_comments_raw[$i]['data']);
|
||||||
|
} else {
|
||||||
|
$info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
|
||||||
|
}
|
||||||
|
$ThisFileInfo_ogg_comments_raw[$i]['data_filename'] = $destination_filename;
|
||||||
|
unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
|
||||||
|
} else {
|
||||||
|
$info['ogg']['comments']['picture'][] = array('data'=>$ThisFileInfo_ogg_comments_raw[$i]['data'], 'image_mime'=>$ThisFileInfo_ogg_comments_raw[$i]['image_mime']);
|
||||||
|
}
|
||||||
|
} while (false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Replay Gain Adjustment
|
||||||
|
// http://privatewww.essex.ac.uk/~djmrob/replaygain/
|
||||||
|
if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
|
||||||
|
foreach ($info['ogg']['comments'] as $index => $commentvalue) {
|
||||||
|
switch ($index) {
|
||||||
|
case 'rg_audiophile':
|
||||||
|
case 'replaygain_album_gain':
|
||||||
|
$info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
|
||||||
|
unset($info['ogg']['comments'][$index]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rg_radio':
|
||||||
|
case 'replaygain_track_gain':
|
||||||
|
$info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
|
||||||
|
unset($info['ogg']['comments'][$index]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'replaygain_album_peak':
|
||||||
|
$info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
|
||||||
|
unset($info['ogg']['comments'][$index]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rg_peak':
|
||||||
|
case 'replaygain_track_peak':
|
||||||
|
$info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
|
||||||
|
unset($info['ogg']['comments'][$index]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'replaygain_reference_loudness':
|
||||||
|
$info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
|
||||||
|
unset($info['ogg']['comments'][$index]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// do nothing
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $OriginalOffset, SEEK_SET);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function SpeexBandModeLookup($mode) {
|
||||||
|
static $SpeexBandModeLookup = array();
|
||||||
|
if (empty($SpeexBandModeLookup)) {
|
||||||
|
$SpeexBandModeLookup[0] = 'narrow';
|
||||||
|
$SpeexBandModeLookup[1] = 'wide';
|
||||||
|
$SpeexBandModeLookup[2] = 'ultra-wide';
|
||||||
|
}
|
||||||
|
return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
|
||||||
|
for ($i = 0; $i < $SegmentNumber; $i++) {
|
||||||
|
$segmentlength = 0;
|
||||||
|
foreach ($OggInfoArray['segment_table'] as $key => $value) {
|
||||||
|
$segmentlength += $value;
|
||||||
|
if ($value < 255) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $segmentlength;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static function get_quality_from_nominal_bitrate($nominal_bitrate) {
|
||||||
|
|
||||||
|
// decrease precision
|
||||||
|
$nominal_bitrate = $nominal_bitrate / 1000;
|
||||||
|
|
||||||
|
if ($nominal_bitrate < 128) {
|
||||||
|
// q-1 to q4
|
||||||
|
$qval = ($nominal_bitrate - 64) / 16;
|
||||||
|
} elseif ($nominal_bitrate < 256) {
|
||||||
|
// q4 to q8
|
||||||
|
$qval = $nominal_bitrate / 32;
|
||||||
|
} elseif ($nominal_bitrate < 320) {
|
||||||
|
// q8 to q9
|
||||||
|
$qval = ($nominal_bitrate + 256) / 64;
|
||||||
|
} else {
|
||||||
|
// q9 to q10
|
||||||
|
$qval = ($nominal_bitrate + 1300) / 180;
|
||||||
|
}
|
||||||
|
//return $qval; // 5.031324
|
||||||
|
//return intval($qval); // 5
|
||||||
|
return round($qval, 1); // 5 or 4.9
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
429
app/library/getid3/module.audio.optimfrog.php
Normal file
429
app/library/getid3/module.audio.optimfrog.php
Normal file
|
@ -0,0 +1,429 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.optimfrog.php //
|
||||||
|
// module for analyzing OptimFROG audio files //
|
||||||
|
// dependencies: module.audio.riff.php //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||||
|
|
||||||
|
class getid3_optimfrog extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'ofr';
|
||||||
|
$info['audio']['dataformat'] = 'ofr';
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
$info['audio']['lossless'] = true;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$OFRheader = fread($this->getid3->fp, 8);
|
||||||
|
if (substr($OFRheader, 0, 5) == '*RIFF') {
|
||||||
|
|
||||||
|
return $this->ParseOptimFROGheader42();
|
||||||
|
|
||||||
|
} elseif (substr($OFRheader, 0, 3) == 'OFR') {
|
||||||
|
|
||||||
|
return $this->ParseOptimFROGheader45();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['error'][] = 'Expecting "*RIFF" or "OFR " at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($OFRheader).'"';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ParseOptimFROGheader42() {
|
||||||
|
// for fileformat of v4.21 and older
|
||||||
|
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$OptimFROGheaderData = fread($this->getid3->fp, 45);
|
||||||
|
$info['avdataoffset'] = 45;
|
||||||
|
|
||||||
|
$OptimFROGencoderVersion_raw = getid3_lib::LittleEndian2Int(substr($OptimFROGheaderData, 0, 1));
|
||||||
|
$OptimFROGencoderVersion_major = floor($OptimFROGencoderVersion_raw / 10);
|
||||||
|
$OptimFROGencoderVersion_minor = $OptimFROGencoderVersion_raw - ($OptimFROGencoderVersion_major * 10);
|
||||||
|
$RIFFdata = substr($OptimFROGheaderData, 1, 44);
|
||||||
|
$OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8;
|
||||||
|
$OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44;
|
||||||
|
|
||||||
|
if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) {
|
||||||
|
$info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize);
|
||||||
|
fseek($this->getid3->fp, $info['avdataend'], SEEK_SET);
|
||||||
|
$RIFFdata .= fread($this->getid3->fp, $OrignalRIFFheaderSize - $OrignalRIFFdataSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// move the data chunk after all other chunks (if any)
|
||||||
|
// so that the RIFF parser doesn't see EOF when trying
|
||||||
|
// to skip over the data chunk
|
||||||
|
$RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8);
|
||||||
|
|
||||||
|
$getid3_temp = new getID3();
|
||||||
|
$getid3_temp->openfile($this->getid3->filename);
|
||||||
|
$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
|
||||||
|
$getid3_temp->info['avdataend'] = $info['avdataend'];
|
||||||
|
$getid3_riff = new getid3_riff($getid3_temp);
|
||||||
|
$getid3_riff->ParseRIFFdata($RIFFdata);
|
||||||
|
$info['riff'] = $getid3_temp->info['riff'];
|
||||||
|
|
||||||
|
$info['audio']['encoder'] = 'OptimFROG '.$OptimFROGencoderVersion_major.'.'.$OptimFROGencoderVersion_minor;
|
||||||
|
$info['audio']['channels'] = $info['riff']['audio'][0]['channels'];
|
||||||
|
$info['audio']['sample_rate'] = $info['riff']['audio'][0]['sample_rate'];
|
||||||
|
$info['audio']['bits_per_sample'] = $info['riff']['audio'][0]['bits_per_sample'];
|
||||||
|
$info['playtime_seconds'] = $OrignalRIFFdataSize / ($info['audio']['channels'] * $info['audio']['sample_rate'] * ($info['audio']['bits_per_sample'] / 8));
|
||||||
|
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
|
||||||
|
unset($getid3_riff, $getid3_temp, $RIFFdata);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ParseOptimFROGheader45() {
|
||||||
|
// for fileformat of v4.50a and higher
|
||||||
|
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
$RIFFdata = '';
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
while (!feof($this->getid3->fp) && (ftell($this->getid3->fp) < $info['avdataend'])) {
|
||||||
|
$BlockOffset = ftell($this->getid3->fp);
|
||||||
|
$BlockData = fread($this->getid3->fp, 8);
|
||||||
|
$offset = 8;
|
||||||
|
$BlockName = substr($BlockData, 0, 4);
|
||||||
|
$BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4));
|
||||||
|
|
||||||
|
if ($BlockName == 'OFRX') {
|
||||||
|
$BlockName = 'OFR ';
|
||||||
|
}
|
||||||
|
if (!isset($info['ofr'][$BlockName])) {
|
||||||
|
$info['ofr'][$BlockName] = array();
|
||||||
|
}
|
||||||
|
$thisfile_ofr_thisblock = &$info['ofr'][$BlockName];
|
||||||
|
|
||||||
|
switch ($BlockName) {
|
||||||
|
case 'OFR ':
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||||
|
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||||
|
|
||||||
|
$info['audio']['encoder'] = 'OptimFROG 4.50 alpha';
|
||||||
|
switch ($BlockSize) {
|
||||||
|
case 12:
|
||||||
|
case 15:
|
||||||
|
// good
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['warning'][] = '"'.$BlockName.'" contains more data than expected (expected 12 or 15 bytes, found '.$BlockSize.' bytes)';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$BlockData .= fread($this->getid3->fp, $BlockSize);
|
||||||
|
|
||||||
|
$thisfile_ofr_thisblock['total_samples'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 6));
|
||||||
|
$offset += 6;
|
||||||
|
$thisfile_ofr_thisblock['raw']['sample_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
|
||||||
|
$thisfile_ofr_thisblock['sample_type'] = $this->OptimFROGsampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']);
|
||||||
|
$offset += 1;
|
||||||
|
$thisfile_ofr_thisblock['channel_config'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
|
||||||
|
$thisfile_ofr_thisblock['channels'] = $thisfile_ofr_thisblock['channel_config'];
|
||||||
|
$offset += 1;
|
||||||
|
$thisfile_ofr_thisblock['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
if ($BlockSize > 12) {
|
||||||
|
|
||||||
|
// OFR 4.504b or higher
|
||||||
|
$thisfile_ofr_thisblock['channels'] = $this->OptimFROGchannelConfigNumChannelsLookup($thisfile_ofr_thisblock['channel_config']);
|
||||||
|
$thisfile_ofr_thisblock['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2));
|
||||||
|
$thisfile_ofr_thisblock['encoder'] = $this->OptimFROGencoderNameLookup($thisfile_ofr_thisblock['raw']['encoder_id']);
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_ofr_thisblock['raw']['compression'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
|
||||||
|
$thisfile_ofr_thisblock['compression'] = $this->OptimFROGcompressionLookup($thisfile_ofr_thisblock['raw']['compression']);
|
||||||
|
$thisfile_ofr_thisblock['speedup'] = $this->OptimFROGspeedupLookup($thisfile_ofr_thisblock['raw']['compression']);
|
||||||
|
$offset += 1;
|
||||||
|
|
||||||
|
$info['audio']['encoder'] = 'OptimFROG '.$thisfile_ofr_thisblock['encoder'];
|
||||||
|
$info['audio']['encoder_options'] = '--mode '.$thisfile_ofr_thisblock['compression'];
|
||||||
|
|
||||||
|
if ((($thisfile_ofr_thisblock['raw']['encoder_id'] & 0xF0) >> 4) == 7) { // v4.507
|
||||||
|
if (strtolower(getid3_lib::fileextension($info['filename'])) == 'ofs') {
|
||||||
|
// OptimFROG DualStream format is lossy, but as of v4.507 there is no way to tell the difference
|
||||||
|
// between lossless and lossy other than the file extension.
|
||||||
|
$info['audio']['dataformat'] = 'ofs';
|
||||||
|
$info['audio']['lossless'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['audio']['channels'] = $thisfile_ofr_thisblock['channels'];
|
||||||
|
$info['audio']['sample_rate'] = $thisfile_ofr_thisblock['sample_rate'];
|
||||||
|
$info['audio']['bits_per_sample'] = $this->OptimFROGbitsPerSampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'COMP':
|
||||||
|
// unlike other block types, there CAN be multiple COMP blocks
|
||||||
|
|
||||||
|
$COMPdata['offset'] = $BlockOffset;
|
||||||
|
$COMPdata['size'] = $BlockSize;
|
||||||
|
|
||||||
|
if ($info['avdataoffset'] == 0) {
|
||||||
|
$info['avdataoffset'] = $BlockOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only interested in first 14 bytes (only first 12 needed for v4.50 alpha), not actual audio data
|
||||||
|
$BlockData .= fread($this->getid3->fp, 14);
|
||||||
|
fseek($this->getid3->fp, $BlockSize - 14, SEEK_CUR);
|
||||||
|
|
||||||
|
$COMPdata['crc_32'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$COMPdata['sample_count'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$COMPdata['raw']['sample_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
|
||||||
|
$COMPdata['sample_type'] = $this->OptimFROGsampleTypeLookup($COMPdata['raw']['sample_type']);
|
||||||
|
$offset += 1;
|
||||||
|
$COMPdata['raw']['channel_configuration'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
|
||||||
|
$COMPdata['channel_configuration'] = $this->OptimFROGchannelConfigurationLookup($COMPdata['raw']['channel_configuration']);
|
||||||
|
$offset += 1;
|
||||||
|
$COMPdata['raw']['algorithm_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2));
|
||||||
|
//$COMPdata['algorithm'] = OptimFROGalgorithmNameLookup($COMPdata['raw']['algorithm_id']);
|
||||||
|
$offset += 2;
|
||||||
|
|
||||||
|
if ($info['ofr']['OFR ']['size'] > 12) {
|
||||||
|
|
||||||
|
// OFR 4.504b or higher
|
||||||
|
$COMPdata['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2));
|
||||||
|
$COMPdata['encoder'] = $this->OptimFROGencoderNameLookup($COMPdata['raw']['encoder_id']);
|
||||||
|
$offset += 2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($COMPdata['crc_32'] == 0x454E4F4E) {
|
||||||
|
// ASCII value of 'NONE' - placeholder value in v4.50a
|
||||||
|
$COMPdata['crc_32'] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_ofr_thisblock[] = $COMPdata;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'HEAD':
|
||||||
|
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||||
|
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||||
|
|
||||||
|
$RIFFdata .= fread($this->getid3->fp, $BlockSize);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'TAIL':
|
||||||
|
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||||
|
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||||
|
|
||||||
|
if ($BlockSize > 0) {
|
||||||
|
$RIFFdata .= fread($this->getid3->fp, $BlockSize);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'RECV':
|
||||||
|
// block contains no useful meta data - simply note and skip
|
||||||
|
|
||||||
|
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||||
|
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'APET':
|
||||||
|
// APEtag v2
|
||||||
|
|
||||||
|
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||||
|
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||||
|
$info['warning'][] = 'APEtag processing inside OptimFROG not supported in this version ('.$this->getid3->version().') of getID3()';
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'MD5 ':
|
||||||
|
// APEtag v2
|
||||||
|
|
||||||
|
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||||
|
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||||
|
|
||||||
|
if ($BlockSize == 16) {
|
||||||
|
|
||||||
|
$thisfile_ofr_thisblock['md5_binary'] = fread($this->getid3->fp, $BlockSize);
|
||||||
|
$thisfile_ofr_thisblock['md5_string'] = getid3_lib::PrintHexBytes($thisfile_ofr_thisblock['md5_binary'], true, false, false);
|
||||||
|
$info['md5_data_source'] = $thisfile_ofr_thisblock['md5_string'];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['warning'][] = 'Expecting block size of 16 in "MD5 " chunk, found '.$BlockSize.' instead';
|
||||||
|
fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
|
||||||
|
$thisfile_ofr_thisblock['size'] = $BlockSize;
|
||||||
|
|
||||||
|
$info['warning'][] = 'Unhandled OptimFROG block type "'.$BlockName.'" at offset '.$thisfile_ofr_thisblock['offset'];
|
||||||
|
fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($info['ofr']['TAIL']['offset'])) {
|
||||||
|
$info['avdataend'] = $info['ofr']['TAIL']['offset'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = (float) $info['ofr']['OFR ']['total_samples'] / ($info['audio']['channels'] * $info['audio']['sample_rate']);
|
||||||
|
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
|
||||||
|
// move the data chunk after all other chunks (if any)
|
||||||
|
// so that the RIFF parser doesn't see EOF when trying
|
||||||
|
// to skip over the data chunk
|
||||||
|
$RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8);
|
||||||
|
|
||||||
|
$getid3_temp = new getID3();
|
||||||
|
$getid3_temp->openfile($this->getid3->filename);
|
||||||
|
$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
|
||||||
|
$getid3_temp->info['avdataend'] = $info['avdataend'];
|
||||||
|
$getid3_riff = new getid3_riff($getid3_temp);
|
||||||
|
$getid3_riff->ParseRIFFdata($RIFFdata);
|
||||||
|
$info['riff'] = $getid3_temp->info['riff'];
|
||||||
|
|
||||||
|
unset($getid3_riff, $getid3_temp, $RIFFdata);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static function OptimFROGsampleTypeLookup($SampleType) {
|
||||||
|
static $OptimFROGsampleTypeLookup = array(
|
||||||
|
0 => 'unsigned int (8-bit)',
|
||||||
|
1 => 'signed int (8-bit)',
|
||||||
|
2 => 'unsigned int (16-bit)',
|
||||||
|
3 => 'signed int (16-bit)',
|
||||||
|
4 => 'unsigned int (24-bit)',
|
||||||
|
5 => 'signed int (24-bit)',
|
||||||
|
6 => 'unsigned int (32-bit)',
|
||||||
|
7 => 'signed int (32-bit)',
|
||||||
|
8 => 'float 0.24 (32-bit)',
|
||||||
|
9 => 'float 16.8 (32-bit)',
|
||||||
|
10 => 'float 24.0 (32-bit)'
|
||||||
|
);
|
||||||
|
return (isset($OptimFROGsampleTypeLookup[$SampleType]) ? $OptimFROGsampleTypeLookup[$SampleType] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function OptimFROGbitsPerSampleTypeLookup($SampleType) {
|
||||||
|
static $OptimFROGbitsPerSampleTypeLookup = array(
|
||||||
|
0 => 8,
|
||||||
|
1 => 8,
|
||||||
|
2 => 16,
|
||||||
|
3 => 16,
|
||||||
|
4 => 24,
|
||||||
|
5 => 24,
|
||||||
|
6 => 32,
|
||||||
|
7 => 32,
|
||||||
|
8 => 32,
|
||||||
|
9 => 32,
|
||||||
|
10 => 32
|
||||||
|
);
|
||||||
|
return (isset($OptimFROGbitsPerSampleTypeLookup[$SampleType]) ? $OptimFROGbitsPerSampleTypeLookup[$SampleType] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function OptimFROGchannelConfigurationLookup($ChannelConfiguration) {
|
||||||
|
static $OptimFROGchannelConfigurationLookup = array(
|
||||||
|
0 => 'mono',
|
||||||
|
1 => 'stereo'
|
||||||
|
);
|
||||||
|
return (isset($OptimFROGchannelConfigurationLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigurationLookup[$ChannelConfiguration] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration) {
|
||||||
|
static $OptimFROGchannelConfigNumChannelsLookup = array(
|
||||||
|
0 => 1,
|
||||||
|
1 => 2
|
||||||
|
);
|
||||||
|
return (isset($OptimFROGchannelConfigNumChannelsLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigNumChannelsLookup[$ChannelConfiguration] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// static function OptimFROGalgorithmNameLookup($AlgorithID) {
|
||||||
|
// static $OptimFROGalgorithmNameLookup = array();
|
||||||
|
// return (isset($OptimFROGalgorithmNameLookup[$AlgorithID]) ? $OptimFROGalgorithmNameLookup[$AlgorithID] : false);
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
static function OptimFROGencoderNameLookup($EncoderID) {
|
||||||
|
// version = (encoderID >> 4) + 4500
|
||||||
|
// system = encoderID & 0xF
|
||||||
|
|
||||||
|
$EncoderVersion = number_format(((($EncoderID & 0xF0) >> 4) + 4500) / 1000, 3);
|
||||||
|
$EncoderSystemID = ($EncoderID & 0x0F);
|
||||||
|
|
||||||
|
static $OptimFROGencoderSystemLookup = array(
|
||||||
|
0x00 => 'Windows console',
|
||||||
|
0x01 => 'Linux console',
|
||||||
|
0x0F => 'unknown'
|
||||||
|
);
|
||||||
|
return $EncoderVersion.' ('.(isset($OptimFROGencoderSystemLookup[$EncoderSystemID]) ? $OptimFROGencoderSystemLookup[$EncoderSystemID] : 'undefined encoder type (0x'.dechex($EncoderSystemID).')').')';
|
||||||
|
}
|
||||||
|
|
||||||
|
static function OptimFROGcompressionLookup($CompressionID) {
|
||||||
|
// mode = compression >> 3
|
||||||
|
// speedup = compression & 0x07
|
||||||
|
|
||||||
|
$CompressionModeID = ($CompressionID & 0xF8) >> 3;
|
||||||
|
//$CompressionSpeedupID = ($CompressionID & 0x07);
|
||||||
|
|
||||||
|
static $OptimFROGencoderModeLookup = array(
|
||||||
|
0x00 => 'fast',
|
||||||
|
0x01 => 'normal',
|
||||||
|
0x02 => 'high',
|
||||||
|
0x03 => 'extra', // extranew (some versions)
|
||||||
|
0x04 => 'best', // bestnew (some versions)
|
||||||
|
0x05 => 'ultra',
|
||||||
|
0x06 => 'insane',
|
||||||
|
0x07 => 'highnew',
|
||||||
|
0x08 => 'extranew',
|
||||||
|
0x09 => 'bestnew'
|
||||||
|
);
|
||||||
|
return (isset($OptimFROGencoderModeLookup[$CompressionModeID]) ? $OptimFROGencoderModeLookup[$CompressionModeID] : 'undefined mode (0x'.str_pad(dechex($CompressionModeID), 2, '0', STR_PAD_LEFT).')');
|
||||||
|
}
|
||||||
|
|
||||||
|
static function OptimFROGspeedupLookup($CompressionID) {
|
||||||
|
// mode = compression >> 3
|
||||||
|
// speedup = compression & 0x07
|
||||||
|
|
||||||
|
//$CompressionModeID = ($CompressionID & 0xF8) >> 3;
|
||||||
|
$CompressionSpeedupID = ($CompressionID & 0x07);
|
||||||
|
|
||||||
|
static $OptimFROGencoderSpeedupLookup = array(
|
||||||
|
0x00 => '1x',
|
||||||
|
0x01 => '2x',
|
||||||
|
0x02 => '4x'
|
||||||
|
);
|
||||||
|
return (isset($OptimFROGencoderSpeedupLookup[$CompressionSpeedupID]) ? $OptimFROGencoderSpeedupLookup[$CompressionSpeedupID] : 'undefined mode (0x'.dechex($CompressionSpeedupID));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
94
app/library/getid3/module.audio.rkau.php
Normal file
94
app/library/getid3/module.audio.rkau.php
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.shorten.php //
|
||||||
|
// module for analyzing Shorten Audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_rkau extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$RKAUHeader = fread($this->getid3->fp, 20);
|
||||||
|
$magic = 'RKA';
|
||||||
|
if (substr($RKAUHeader, 0, 3) != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($RKAUHeader, 0, 3)).'"';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['fileformat'] = 'rkau';
|
||||||
|
$info['audio']['dataformat'] = 'rkau';
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
|
||||||
|
$info['rkau']['raw']['version'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 3, 1));
|
||||||
|
$info['rkau']['version'] = '1.'.str_pad($info['rkau']['raw']['version'] & 0x0F, 2, '0', STR_PAD_LEFT);
|
||||||
|
if (($info['rkau']['version'] > 1.07) || ($info['rkau']['version'] < 1.06)) {
|
||||||
|
$info['error'][] = 'This version of getID3() ['.$this->getid3->version().'] can only parse RKAU files v1.06 and 1.07 (this file is v'.$info['rkau']['version'].')';
|
||||||
|
unset($info['rkau']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['rkau']['source_bytes'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 4, 4));
|
||||||
|
$info['rkau']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 8, 4));
|
||||||
|
$info['rkau']['channels'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 12, 1));
|
||||||
|
$info['rkau']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 13, 1));
|
||||||
|
|
||||||
|
$info['rkau']['raw']['quality'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 14, 1));
|
||||||
|
$this->RKAUqualityLookup($info['rkau']);
|
||||||
|
|
||||||
|
$info['rkau']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 15, 1));
|
||||||
|
$info['rkau']['flags']['joint_stereo'] = (bool) (!($info['rkau']['raw']['flags'] & 0x01));
|
||||||
|
$info['rkau']['flags']['streaming'] = (bool) ($info['rkau']['raw']['flags'] & 0x02);
|
||||||
|
$info['rkau']['flags']['vrq_lossy_mode'] = (bool) ($info['rkau']['raw']['flags'] & 0x04);
|
||||||
|
|
||||||
|
if ($info['rkau']['flags']['streaming']) {
|
||||||
|
$info['avdataoffset'] += 20;
|
||||||
|
$info['rkau']['compressed_bytes'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 16, 4));
|
||||||
|
} else {
|
||||||
|
$info['avdataoffset'] += 16;
|
||||||
|
$info['rkau']['compressed_bytes'] = $info['avdataend'] - $info['avdataoffset'] - 1;
|
||||||
|
}
|
||||||
|
// Note: compressed_bytes does not always equal what appears to be the actual number of compressed bytes,
|
||||||
|
// sometimes it's more, sometimes less. No idea why(?)
|
||||||
|
|
||||||
|
$info['audio']['lossless'] = $info['rkau']['lossless'];
|
||||||
|
$info['audio']['channels'] = $info['rkau']['channels'];
|
||||||
|
$info['audio']['bits_per_sample'] = $info['rkau']['bits_per_sample'];
|
||||||
|
$info['audio']['sample_rate'] = $info['rkau']['sample_rate'];
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = $info['rkau']['source_bytes'] / ($info['rkau']['sample_rate'] * $info['rkau']['channels'] * ($info['rkau']['bits_per_sample'] / 8));
|
||||||
|
$info['audio']['bitrate'] = ($info['rkau']['compressed_bytes'] * 8) / $info['playtime_seconds'];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RKAUqualityLookup(&$RKAUdata) {
|
||||||
|
$level = ($RKAUdata['raw']['quality'] & 0xF0) >> 4;
|
||||||
|
$quality = $RKAUdata['raw']['quality'] & 0x0F;
|
||||||
|
|
||||||
|
$RKAUdata['lossless'] = (($quality == 0) ? true : false);
|
||||||
|
$RKAUdata['compression_level'] = $level + 1;
|
||||||
|
if (!$RKAUdata['lossless']) {
|
||||||
|
$RKAUdata['quality_setting'] = $quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
183
app/library/getid3/module.audio.shorten.php
Normal file
183
app/library/getid3/module.audio.shorten.php
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.shorten.php //
|
||||||
|
// module for analyzing Shorten Audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_shorten extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
|
||||||
|
$ShortenHeader = fread($this->getid3->fp, 8);
|
||||||
|
$magic = 'ajkg';
|
||||||
|
if (substr($ShortenHeader, 0, 4) != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($ShortenHeader, 0, 4)).'"';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['fileformat'] = 'shn';
|
||||||
|
$info['audio']['dataformat'] = 'shn';
|
||||||
|
$info['audio']['lossless'] = true;
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
|
||||||
|
$info['shn']['version'] = getid3_lib::LittleEndian2Int(substr($ShortenHeader, 4, 1));
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataend'] - 12, SEEK_SET);
|
||||||
|
$SeekTableSignatureTest = fread($this->getid3->fp, 12);
|
||||||
|
$info['shn']['seektable']['present'] = (bool) (substr($SeekTableSignatureTest, 4, 8) == 'SHNAMPSK');
|
||||||
|
if ($info['shn']['seektable']['present']) {
|
||||||
|
$info['shn']['seektable']['length'] = getid3_lib::LittleEndian2Int(substr($SeekTableSignatureTest, 0, 4));
|
||||||
|
$info['shn']['seektable']['offset'] = $info['avdataend'] - $info['shn']['seektable']['length'];
|
||||||
|
fseek($this->getid3->fp, $info['shn']['seektable']['offset'], SEEK_SET);
|
||||||
|
$SeekTableMagic = fread($this->getid3->fp, 4);
|
||||||
|
$magic = 'SEEK';
|
||||||
|
if ($SeekTableMagic != $magic) {
|
||||||
|
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['shn']['seektable']['offset'].', found "'.getid3_lib::PrintHexBytes($SeekTableMagic).'"';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// typedef struct tag_TSeekEntry
|
||||||
|
// {
|
||||||
|
// unsigned long SampleNumber;
|
||||||
|
// unsigned long SHNFileByteOffset;
|
||||||
|
// unsigned long SHNLastBufferReadPosition;
|
||||||
|
// unsigned short SHNByteGet;
|
||||||
|
// unsigned short SHNBufferOffset;
|
||||||
|
// unsigned short SHNFileBitOffset;
|
||||||
|
// unsigned long SHNGBuffer;
|
||||||
|
// unsigned short SHNBitShift;
|
||||||
|
// long CBuf0[3];
|
||||||
|
// long CBuf1[3];
|
||||||
|
// long Offset0[4];
|
||||||
|
// long Offset1[4];
|
||||||
|
// }TSeekEntry;
|
||||||
|
|
||||||
|
$SeekTableData = fread($this->getid3->fp, $info['shn']['seektable']['length'] - 16);
|
||||||
|
$info['shn']['seektable']['entry_count'] = floor(strlen($SeekTableData) / 80);
|
||||||
|
//$info['shn']['seektable']['entries'] = array();
|
||||||
|
//$SeekTableOffset = 0;
|
||||||
|
//for ($i = 0; $i < $info['shn']['seektable']['entry_count']; $i++) {
|
||||||
|
// $SeekTableEntry['sample_number'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||||
|
// $SeekTableOffset += 4;
|
||||||
|
// $SeekTableEntry['shn_file_byte_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||||
|
// $SeekTableOffset += 4;
|
||||||
|
// $SeekTableEntry['shn_last_buffer_read_position'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||||
|
// $SeekTableOffset += 4;
|
||||||
|
// $SeekTableEntry['shn_byte_get'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2));
|
||||||
|
// $SeekTableOffset += 2;
|
||||||
|
// $SeekTableEntry['shn_buffer_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2));
|
||||||
|
// $SeekTableOffset += 2;
|
||||||
|
// $SeekTableEntry['shn_file_bit_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2));
|
||||||
|
// $SeekTableOffset += 2;
|
||||||
|
// $SeekTableEntry['shn_gbuffer'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||||
|
// $SeekTableOffset += 4;
|
||||||
|
// $SeekTableEntry['shn_bit_shift'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2));
|
||||||
|
// $SeekTableOffset += 2;
|
||||||
|
// for ($j = 0; $j < 3; $j++) {
|
||||||
|
// $SeekTableEntry['cbuf0'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||||
|
// $SeekTableOffset += 4;
|
||||||
|
// }
|
||||||
|
// for ($j = 0; $j < 3; $j++) {
|
||||||
|
// $SeekTableEntry['cbuf1'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||||
|
// $SeekTableOffset += 4;
|
||||||
|
// }
|
||||||
|
// for ($j = 0; $j < 4; $j++) {
|
||||||
|
// $SeekTableEntry['offset0'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||||
|
// $SeekTableOffset += 4;
|
||||||
|
// }
|
||||||
|
// for ($j = 0; $j < 4; $j++) {
|
||||||
|
// $SeekTableEntry['offset1'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
|
||||||
|
// $SeekTableOffset += 4;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// $info['shn']['seektable']['entries'][] = $SeekTableEntry;
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
|
||||||
|
$info['error'][] = 'PHP running in Safe Mode - backtick operator not available, cannot run shntool to analyze Shorten files';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GETID3_OS_ISWINDOWS) {
|
||||||
|
|
||||||
|
$RequiredFiles = array('shorten.exe', 'cygwin1.dll', 'head.exe');
|
||||||
|
foreach ($RequiredFiles as $required_file) {
|
||||||
|
if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) {
|
||||||
|
$info['error'][] = GETID3_HELPERAPPSDIR.$required_file.' does not exist';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$commandline = GETID3_HELPERAPPSDIR.'shorten.exe -x "'.$info['filenamepath'].'" - | '.GETID3_HELPERAPPSDIR.'head.exe -c 64';
|
||||||
|
$commandline = str_replace('/', '\\', $commandline);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
static $shorten_present;
|
||||||
|
if (!isset($shorten_present)) {
|
||||||
|
$shorten_present = file_exists('/usr/local/bin/shorten') || `which shorten`;
|
||||||
|
}
|
||||||
|
if (!$shorten_present) {
|
||||||
|
$info['error'][] = 'shorten binary was not found in path or /usr/local/bin';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$commandline = (file_exists('/usr/local/bin/shorten') ? '/usr/local/bin/' : '' ) . 'shorten -x '.escapeshellarg($info['filenamepath']).' - | head -c 64';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = `$commandline`;
|
||||||
|
|
||||||
|
if (!empty($output) && (substr($output, 12, 4) == 'fmt ')) {
|
||||||
|
|
||||||
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||||
|
|
||||||
|
$fmt_size = getid3_lib::LittleEndian2Int(substr($output, 16, 4));
|
||||||
|
$DecodedWAVFORMATEX = getid3_riff::RIFFparseWAVEFORMATex(substr($output, 20, $fmt_size));
|
||||||
|
$info['audio']['channels'] = $DecodedWAVFORMATEX['channels'];
|
||||||
|
$info['audio']['bits_per_sample'] = $DecodedWAVFORMATEX['bits_per_sample'];
|
||||||
|
$info['audio']['sample_rate'] = $DecodedWAVFORMATEX['sample_rate'];
|
||||||
|
|
||||||
|
if (substr($output, 20 + $fmt_size, 4) == 'data') {
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = getid3_lib::LittleEndian2Int(substr($output, 20 + 4 + $fmt_size, 4)) / $DecodedWAVFORMATEX['raw']['nAvgBytesPerSec'];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['error'][] = 'shorten failed to decode DATA chunk to expected location, cannot determine playtime';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['error'][] = 'shorten failed to decode file to WAV for parsing';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
109
app/library/getid3/module.audio.tta.php
Normal file
109
app/library/getid3/module.audio.tta.php
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.tta.php //
|
||||||
|
// module for analyzing TTA Audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_tta extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'tta';
|
||||||
|
$info['audio']['dataformat'] = 'tta';
|
||||||
|
$info['audio']['lossless'] = true;
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$ttaheader = fread($this->getid3->fp, 26);
|
||||||
|
|
||||||
|
$info['tta']['magic'] = substr($ttaheader, 0, 3);
|
||||||
|
$magic = 'TTA';
|
||||||
|
if ($info['tta']['magic'] != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['tta']['magic']).'"';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['audio']);
|
||||||
|
unset($info['tta']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($ttaheader{3}) {
|
||||||
|
case "\x01": // TTA v1.x
|
||||||
|
case "\x02": // TTA v1.x
|
||||||
|
case "\x03": // TTA v1.x
|
||||||
|
// "It was the demo-version of the TTA encoder. There is no released format with such header. TTA encoder v1 is not supported about a year."
|
||||||
|
$info['tta']['major_version'] = 1;
|
||||||
|
$info['avdataoffset'] += 16;
|
||||||
|
|
||||||
|
$info['tta']['compression_level'] = ord($ttaheader{3});
|
||||||
|
$info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2));
|
||||||
|
$info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2));
|
||||||
|
$info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 4));
|
||||||
|
$info['tta']['samples_per_channel'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 12, 4));
|
||||||
|
|
||||||
|
$info['audio']['encoder_options'] = '-e'.$info['tta']['compression_level'];
|
||||||
|
$info['playtime_seconds'] = $info['tta']['samples_per_channel'] / $info['tta']['sample_rate'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '2': // TTA v2.x
|
||||||
|
// "I have hurried to release the TTA 2.0 encoder. Format documentation is removed from our site. This format still in development. Please wait the TTA2 format, encoder v4."
|
||||||
|
$info['tta']['major_version'] = 2;
|
||||||
|
$info['avdataoffset'] += 20;
|
||||||
|
|
||||||
|
$info['tta']['compression_level'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2));
|
||||||
|
$info['tta']['audio_format'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2));
|
||||||
|
$info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 2));
|
||||||
|
$info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 10, 2));
|
||||||
|
$info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 12, 4));
|
||||||
|
$info['tta']['data_length'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 16, 4));
|
||||||
|
|
||||||
|
$info['audio']['encoder_options'] = '-e'.$info['tta']['compression_level'];
|
||||||
|
$info['playtime_seconds'] = $info['tta']['data_length'] / $info['tta']['sample_rate'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '1': // TTA v3.x
|
||||||
|
// "This is a first stable release of the TTA format. It will be supported by the encoders v3 or higher."
|
||||||
|
$info['tta']['major_version'] = 3;
|
||||||
|
$info['avdataoffset'] += 26;
|
||||||
|
|
||||||
|
$info['tta']['audio_format'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2)); // getid3_riff::RIFFwFormatTagLookup()
|
||||||
|
$info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2));
|
||||||
|
$info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 2));
|
||||||
|
$info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 10, 4));
|
||||||
|
$info['tta']['data_length'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 14, 4));
|
||||||
|
$info['tta']['crc32_footer'] = substr($ttaheader, 18, 4);
|
||||||
|
$info['tta']['seek_point'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 22, 4));
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = $info['tta']['data_length'] / $info['tta']['sample_rate'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['error'][] = 'This version of getID3() ['.$this->getid3->version().'] only knows how to handle TTA v1 and v2 - it may not work correctly with this file which appears to be TTA v'.$ttaheader{3};
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['audio']['encoder'] = 'TTA v'.$info['tta']['major_version'];
|
||||||
|
$info['audio']['bits_per_sample'] = $info['tta']['bits_per_sample'];
|
||||||
|
$info['audio']['sample_rate'] = $info['tta']['sample_rate'];
|
||||||
|
$info['audio']['channels'] = $info['tta']['channels'];
|
||||||
|
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
207
app/library/getid3/module.audio.voc.php
Normal file
207
app/library/getid3/module.audio.voc.php
Normal file
|
@ -0,0 +1,207 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.voc.php //
|
||||||
|
// module for analyzing Creative VOC Audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_voc extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$OriginalAVdataOffset = $info['avdataoffset'];
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$VOCheader = fread($this->getid3->fp, 26);
|
||||||
|
|
||||||
|
$magic = 'Creative Voice File';
|
||||||
|
if (substr($VOCheader, 0, 19) != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($VOCheader, 0, 19)).'"';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcuts
|
||||||
|
$thisfile_audio = &$info['audio'];
|
||||||
|
$info['voc'] = array();
|
||||||
|
$thisfile_voc = &$info['voc'];
|
||||||
|
|
||||||
|
$info['fileformat'] = 'voc';
|
||||||
|
$thisfile_audio['dataformat'] = 'voc';
|
||||||
|
$thisfile_audio['bitrate_mode'] = 'cbr';
|
||||||
|
$thisfile_audio['lossless'] = true;
|
||||||
|
$thisfile_audio['channels'] = 1; // might be overriden below
|
||||||
|
$thisfile_audio['bits_per_sample'] = 8; // might be overriden below
|
||||||
|
|
||||||
|
// byte # Description
|
||||||
|
// ------ ------------------------------------------
|
||||||
|
// 00-12 'Creative Voice File'
|
||||||
|
// 13 1A (eof to abort printing of file)
|
||||||
|
// 14-15 Offset of first datablock in .voc file (std 1A 00 in Intel Notation)
|
||||||
|
// 16-17 Version number (minor,major) (VOC-HDR puts 0A 01)
|
||||||
|
// 18-19 2's Comp of Ver. # + 1234h (VOC-HDR puts 29 11)
|
||||||
|
|
||||||
|
$thisfile_voc['header']['datablock_offset'] = getid3_lib::LittleEndian2Int(substr($VOCheader, 20, 2));
|
||||||
|
$thisfile_voc['header']['minor_version'] = getid3_lib::LittleEndian2Int(substr($VOCheader, 22, 1));
|
||||||
|
$thisfile_voc['header']['major_version'] = getid3_lib::LittleEndian2Int(substr($VOCheader, 23, 1));
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
$BlockOffset = ftell($this->getid3->fp);
|
||||||
|
$BlockData = fread($this->getid3->fp, 4);
|
||||||
|
$BlockType = ord($BlockData{0});
|
||||||
|
$BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 1, 3));
|
||||||
|
$ThisBlock = array();
|
||||||
|
|
||||||
|
getid3_lib::safe_inc($thisfile_voc['blocktypes'][$BlockType], 1);
|
||||||
|
switch ($BlockType) {
|
||||||
|
case 0: // Terminator
|
||||||
|
// do nothing, we'll break out of the loop down below
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // Sound data
|
||||||
|
$BlockData .= fread($this->getid3->fp, 2);
|
||||||
|
if ($info['avdataoffset'] <= $OriginalAVdataOffset) {
|
||||||
|
$info['avdataoffset'] = ftell($this->getid3->fp);
|
||||||
|
}
|
||||||
|
fseek($this->getid3->fp, $BlockSize - 2, SEEK_CUR);
|
||||||
|
|
||||||
|
$ThisBlock['sample_rate_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 1));
|
||||||
|
$ThisBlock['compression_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, 5, 1));
|
||||||
|
|
||||||
|
$ThisBlock['compression_name'] = $this->VOCcompressionTypeLookup($ThisBlock['compression_type']);
|
||||||
|
if ($ThisBlock['compression_type'] <= 3) {
|
||||||
|
$thisfile_voc['compressed_bits_per_sample'] = getid3_lib::CastAsInt(str_replace('-bit', '', $ThisBlock['compression_name']));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less accurate sample_rate calculation than the Extended block (#8) data (but better than nothing if Extended Block is not available)
|
||||||
|
if (empty($thisfile_audio['sample_rate'])) {
|
||||||
|
// SR byte = 256 - (1000000 / sample_rate)
|
||||||
|
$thisfile_audio['sample_rate'] = getid3_lib::trunc((1000000 / (256 - $ThisBlock['sample_rate_id'])) / $thisfile_audio['channels']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // Sound continue
|
||||||
|
case 3: // Silence
|
||||||
|
case 4: // Marker
|
||||||
|
case 6: // Repeat
|
||||||
|
case 7: // End repeat
|
||||||
|
// nothing useful, just skip
|
||||||
|
fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 8: // Extended
|
||||||
|
$BlockData .= fread($this->getid3->fp, 4);
|
||||||
|
|
||||||
|
//00-01 Time Constant:
|
||||||
|
// Mono: 65536 - (256000000 / sample_rate)
|
||||||
|
// Stereo: 65536 - (256000000 / (sample_rate * 2))
|
||||||
|
$ThisBlock['time_constant'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 2));
|
||||||
|
$ThisBlock['pack_method'] = getid3_lib::LittleEndian2Int(substr($BlockData, 6, 1));
|
||||||
|
$ThisBlock['stereo'] = (bool) getid3_lib::LittleEndian2Int(substr($BlockData, 7, 1));
|
||||||
|
|
||||||
|
$thisfile_audio['channels'] = ($ThisBlock['stereo'] ? 2 : 1);
|
||||||
|
$thisfile_audio['sample_rate'] = getid3_lib::trunc((256000000 / (65536 - $ThisBlock['time_constant'])) / $thisfile_audio['channels']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 9: // data block that supersedes blocks 1 and 8. Used for stereo, 16 bit
|
||||||
|
$BlockData .= fread($this->getid3->fp, 12);
|
||||||
|
if ($info['avdataoffset'] <= $OriginalAVdataOffset) {
|
||||||
|
$info['avdataoffset'] = ftell($this->getid3->fp);
|
||||||
|
}
|
||||||
|
fseek($this->getid3->fp, $BlockSize - 12, SEEK_CUR);
|
||||||
|
|
||||||
|
$ThisBlock['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4));
|
||||||
|
$ThisBlock['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($BlockData, 8, 1));
|
||||||
|
$ThisBlock['channels'] = getid3_lib::LittleEndian2Int(substr($BlockData, 9, 1));
|
||||||
|
$ThisBlock['wFormat'] = getid3_lib::LittleEndian2Int(substr($BlockData, 10, 2));
|
||||||
|
|
||||||
|
$ThisBlock['compression_name'] = $this->VOCwFormatLookup($ThisBlock['wFormat']);
|
||||||
|
if ($this->VOCwFormatActualBitsPerSampleLookup($ThisBlock['wFormat'])) {
|
||||||
|
$thisfile_voc['compressed_bits_per_sample'] = $this->VOCwFormatActualBitsPerSampleLookup($ThisBlock['wFormat']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_audio['sample_rate'] = $ThisBlock['sample_rate'];
|
||||||
|
$thisfile_audio['bits_per_sample'] = $ThisBlock['bits_per_sample'];
|
||||||
|
$thisfile_audio['channels'] = $ThisBlock['channels'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['warning'][] = 'Unhandled block type "'.$BlockType.'" at offset '.$BlockOffset;
|
||||||
|
fseek($this->getid3->fp, $BlockSize, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($ThisBlock)) {
|
||||||
|
$ThisBlock['block_offset'] = $BlockOffset;
|
||||||
|
$ThisBlock['block_size'] = $BlockSize;
|
||||||
|
$ThisBlock['block_type_id'] = $BlockType;
|
||||||
|
$thisfile_voc['blocks'][] = $ThisBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (!feof($this->getid3->fp) && ($BlockType != 0));
|
||||||
|
|
||||||
|
// Terminator block doesn't have size field, so seek back 3 spaces
|
||||||
|
fseek($this->getid3->fp, -3, SEEK_CUR);
|
||||||
|
|
||||||
|
ksort($thisfile_voc['blocktypes']);
|
||||||
|
|
||||||
|
if (!empty($thisfile_voc['compressed_bits_per_sample'])) {
|
||||||
|
$info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($thisfile_voc['compressed_bits_per_sample'] * $thisfile_audio['channels'] * $thisfile_audio['sample_rate']);
|
||||||
|
$thisfile_audio['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function VOCcompressionTypeLookup($index) {
|
||||||
|
static $VOCcompressionTypeLookup = array(
|
||||||
|
0 => '8-bit',
|
||||||
|
1 => '4-bit',
|
||||||
|
2 => '2.6-bit',
|
||||||
|
3 => '2-bit'
|
||||||
|
);
|
||||||
|
return (isset($VOCcompressionTypeLookup[$index]) ? $VOCcompressionTypeLookup[$index] : 'Multi DAC ('.($index - 3).') channels');
|
||||||
|
}
|
||||||
|
|
||||||
|
function VOCwFormatLookup($index) {
|
||||||
|
static $VOCwFormatLookup = array(
|
||||||
|
0x0000 => '8-bit unsigned PCM',
|
||||||
|
0x0001 => 'Creative 8-bit to 4-bit ADPCM',
|
||||||
|
0x0002 => 'Creative 8-bit to 3-bit ADPCM',
|
||||||
|
0x0003 => 'Creative 8-bit to 2-bit ADPCM',
|
||||||
|
0x0004 => '16-bit signed PCM',
|
||||||
|
0x0006 => 'CCITT a-Law',
|
||||||
|
0x0007 => 'CCITT u-Law',
|
||||||
|
0x2000 => 'Creative 16-bit to 4-bit ADPCM'
|
||||||
|
);
|
||||||
|
return (isset($VOCwFormatLookup[$index]) ? $VOCwFormatLookup[$index] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function VOCwFormatActualBitsPerSampleLookup($index) {
|
||||||
|
static $VOCwFormatLookup = array(
|
||||||
|
0x0000 => 8,
|
||||||
|
0x0001 => 4,
|
||||||
|
0x0002 => 3,
|
||||||
|
0x0003 => 2,
|
||||||
|
0x0004 => 16,
|
||||||
|
0x0006 => 8,
|
||||||
|
0x0007 => 8,
|
||||||
|
0x2000 => 4
|
||||||
|
);
|
||||||
|
return (isset($VOCwFormatLookup[$index]) ? $VOCwFormatLookup[$index] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
162
app/library/getid3/module.audio.vqf.php
Normal file
162
app/library/getid3/module.audio.vqf.php
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.vqf.php //
|
||||||
|
// module for analyzing VQF audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_vqf extends getid3_handler
|
||||||
|
{
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
// based loosely on code from TTwinVQ by Jurgen Faul <jfaulØgmx*de>
|
||||||
|
// http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html
|
||||||
|
|
||||||
|
$info['fileformat'] = 'vqf';
|
||||||
|
$info['audio']['dataformat'] = 'vqf';
|
||||||
|
$info['audio']['bitrate_mode'] = 'cbr';
|
||||||
|
$info['audio']['lossless'] = false;
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$info['vqf']['raw'] = array();
|
||||||
|
$thisfile_vqf = &$info['vqf'];
|
||||||
|
$thisfile_vqf_raw = &$thisfile_vqf['raw'];
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$VQFheaderData = fread($this->getid3->fp, 16);
|
||||||
|
|
||||||
|
$offset = 0;
|
||||||
|
$thisfile_vqf_raw['header_tag'] = substr($VQFheaderData, $offset, 4);
|
||||||
|
$magic = 'TWIN';
|
||||||
|
if ($thisfile_vqf_raw['header_tag'] != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_vqf_raw['header_tag']).'"';
|
||||||
|
unset($info['vqf']);
|
||||||
|
unset($info['fileformat']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_vqf_raw['version'] = substr($VQFheaderData, $offset, 8);
|
||||||
|
$offset += 8;
|
||||||
|
$thisfile_vqf_raw['size'] = getid3_lib::BigEndian2Int(substr($VQFheaderData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
while (ftell($this->getid3->fp) < $info['avdataend']) {
|
||||||
|
|
||||||
|
$ChunkBaseOffset = ftell($this->getid3->fp);
|
||||||
|
$chunkoffset = 0;
|
||||||
|
$ChunkData = fread($this->getid3->fp, 8);
|
||||||
|
$ChunkName = substr($ChunkData, $chunkoffset, 4);
|
||||||
|
if ($ChunkName == 'DATA') {
|
||||||
|
$info['avdataoffset'] = $ChunkBaseOffset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$chunkoffset += 4;
|
||||||
|
$ChunkSize = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
|
||||||
|
$chunkoffset += 4;
|
||||||
|
if ($ChunkSize > ($info['avdataend'] - ftell($this->getid3->fp))) {
|
||||||
|
$info['error'][] = 'Invalid chunk size ('.$ChunkSize.') for chunk "'.$ChunkName.'" at offset '.$ChunkBaseOffset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($ChunkSize > 0) {
|
||||||
|
$ChunkData .= fread($this->getid3->fp, $ChunkSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($ChunkName) {
|
||||||
|
case 'COMM':
|
||||||
|
// shortcut
|
||||||
|
$thisfile_vqf['COMM'] = array();
|
||||||
|
$thisfile_vqf_COMM = &$thisfile_vqf['COMM'];
|
||||||
|
|
||||||
|
$thisfile_vqf_COMM['channel_mode'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
|
||||||
|
$chunkoffset += 4;
|
||||||
|
$thisfile_vqf_COMM['bitrate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
|
||||||
|
$chunkoffset += 4;
|
||||||
|
$thisfile_vqf_COMM['sample_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
|
||||||
|
$chunkoffset += 4;
|
||||||
|
$thisfile_vqf_COMM['security_level'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
|
||||||
|
$chunkoffset += 4;
|
||||||
|
|
||||||
|
$info['audio']['channels'] = $thisfile_vqf_COMM['channel_mode'] + 1;
|
||||||
|
$info['audio']['sample_rate'] = $this->VQFchannelFrequencyLookup($thisfile_vqf_COMM['sample_rate']);
|
||||||
|
$info['audio']['bitrate'] = $thisfile_vqf_COMM['bitrate'] * 1000;
|
||||||
|
$info['audio']['encoder_options'] = 'CBR' . ceil($info['audio']['bitrate']/1000);
|
||||||
|
|
||||||
|
if ($info['audio']['bitrate'] == 0) {
|
||||||
|
$info['error'][] = 'Corrupt VQF file: bitrate_audio == zero';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NAME':
|
||||||
|
case 'AUTH':
|
||||||
|
case '(c) ':
|
||||||
|
case 'FILE':
|
||||||
|
case 'COMT':
|
||||||
|
case 'ALBM':
|
||||||
|
$thisfile_vqf['comments'][$this->VQFcommentNiceNameLookup($ChunkName)][] = trim(substr($ChunkData, 8));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'DSIZ':
|
||||||
|
$thisfile_vqf['DSIZ'] = getid3_lib::BigEndian2Int(substr($ChunkData, 8, 4));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['warning'][] = 'Unhandled chunk type "'.$ChunkName.'" at offset '.$ChunkBaseOffset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate'];
|
||||||
|
|
||||||
|
if (isset($thisfile_vqf['DSIZ']) && (($thisfile_vqf['DSIZ'] != ($info['avdataend'] - $info['avdataoffset'] - strlen('DATA'))))) {
|
||||||
|
switch ($thisfile_vqf['DSIZ']) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
$info['warning'][] = 'Invalid DSIZ value "'.$thisfile_vqf['DSIZ'].'". This is known to happen with VQF files encoded by Ahead Nero, and seems to be its way of saying this is TwinVQF v'.($thisfile_vqf['DSIZ'] + 1).'.0';
|
||||||
|
$info['audio']['encoder'] = 'Ahead Nero';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['warning'][] = 'Probable corrupted file - should be '.$thisfile_vqf['DSIZ'].' bytes, actually '.($info['avdataend'] - $info['avdataoffset'] - strlen('DATA'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function VQFchannelFrequencyLookup($frequencyid) {
|
||||||
|
static $VQFchannelFrequencyLookup = array(
|
||||||
|
11 => 11025,
|
||||||
|
22 => 22050,
|
||||||
|
44 => 44100
|
||||||
|
);
|
||||||
|
return (isset($VQFchannelFrequencyLookup[$frequencyid]) ? $VQFchannelFrequencyLookup[$frequencyid] : $frequencyid * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function VQFcommentNiceNameLookup($shortname) {
|
||||||
|
static $VQFcommentNiceNameLookup = array(
|
||||||
|
'NAME' => 'title',
|
||||||
|
'AUTH' => 'artist',
|
||||||
|
'(c) ' => 'copyright',
|
||||||
|
'FILE' => 'filename',
|
||||||
|
'COMT' => 'comment',
|
||||||
|
'ALBM' => 'album'
|
||||||
|
);
|
||||||
|
return (isset($VQFcommentNiceNameLookup[$shortname]) ? $VQFcommentNiceNameLookup[$shortname] : $shortname);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
400
app/library/getid3/module.audio.wavpack.php
Normal file
400
app/library/getid3/module.audio.wavpack.php
Normal file
|
@ -0,0 +1,400 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.audio.wavpack.php //
|
||||||
|
// module for analyzing WavPack v4.0+ Audio files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_wavpack extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
|
||||||
|
$wavpackheader = fread($this->getid3->fp, 32);
|
||||||
|
|
||||||
|
if (ftell($this->getid3->fp) >= $info['avdataend']) {
|
||||||
|
break;
|
||||||
|
} elseif (feof($this->getid3->fp)) {
|
||||||
|
break;
|
||||||
|
} elseif (
|
||||||
|
isset($info['wavpack']['blockheader']['total_samples']) &&
|
||||||
|
isset($info['wavpack']['blockheader']['block_samples']) &&
|
||||||
|
($info['wavpack']['blockheader']['total_samples'] > 0) &&
|
||||||
|
($info['wavpack']['blockheader']['block_samples'] > 0) &&
|
||||||
|
(!isset($info['wavpack']['riff_trailer_size']) || ($info['wavpack']['riff_trailer_size'] <= 0)) &&
|
||||||
|
((isset($info['wavpack']['config_flags']['md5_checksum']) && ($info['wavpack']['config_flags']['md5_checksum'] === false)) || !empty($info['md5_data_source']))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$blockheader_offset = ftell($this->getid3->fp) - 32;
|
||||||
|
$blockheader_magic = substr($wavpackheader, 0, 4);
|
||||||
|
$blockheader_size = getid3_lib::LittleEndian2Int(substr($wavpackheader, 4, 4));
|
||||||
|
|
||||||
|
$magic = 'wvpk';
|
||||||
|
if ($blockheader_magic != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$blockheader_offset.', found "'.getid3_lib::PrintHexBytes($blockheader_magic).'"';
|
||||||
|
switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') {
|
||||||
|
case 'wavpack':
|
||||||
|
case 'wvc':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['audio']);
|
||||||
|
unset($info['wavpack']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($info['wavpack']['blockheader']['block_samples']) ||
|
||||||
|
empty($info['wavpack']['blockheader']['total_samples']) ||
|
||||||
|
($info['wavpack']['blockheader']['block_samples'] <= 0) ||
|
||||||
|
($info['wavpack']['blockheader']['total_samples'] <= 0)) {
|
||||||
|
// Also, it is possible that the first block might not have
|
||||||
|
// any samples (block_samples == 0) and in this case you should skip blocks
|
||||||
|
// until you find one with samples because the other information (like
|
||||||
|
// total_samples) are not guaranteed to be correct until (block_samples > 0)
|
||||||
|
|
||||||
|
// Finally, I have defined a format for files in which the length is not known
|
||||||
|
// (for example when raw files are created using pipes). In these cases
|
||||||
|
// total_samples will be -1 and you must seek to the final block to determine
|
||||||
|
// the total number of samples.
|
||||||
|
|
||||||
|
|
||||||
|
$info['audio']['dataformat'] = 'wavpack';
|
||||||
|
$info['fileformat'] = 'wavpack';
|
||||||
|
$info['audio']['lossless'] = true;
|
||||||
|
$info['audio']['bitrate_mode'] = 'vbr';
|
||||||
|
|
||||||
|
$info['wavpack']['blockheader']['offset'] = $blockheader_offset;
|
||||||
|
$info['wavpack']['blockheader']['magic'] = $blockheader_magic;
|
||||||
|
$info['wavpack']['blockheader']['size'] = $blockheader_size;
|
||||||
|
|
||||||
|
if ($info['wavpack']['blockheader']['size'] >= 0x100000) {
|
||||||
|
$info['error'][] = 'Expecting WavPack block size less than "0x100000", found "'.$info['wavpack']['blockheader']['size'].'" at offset '.$info['wavpack']['blockheader']['offset'];
|
||||||
|
switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') {
|
||||||
|
case 'wavpack':
|
||||||
|
case 'wvc':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['audio']);
|
||||||
|
unset($info['wavpack']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['wavpack']['blockheader']['minor_version'] = ord($wavpackheader{8});
|
||||||
|
$info['wavpack']['blockheader']['major_version'] = ord($wavpackheader{9});
|
||||||
|
|
||||||
|
if (($info['wavpack']['blockheader']['major_version'] != 4) ||
|
||||||
|
(($info['wavpack']['blockheader']['minor_version'] < 4) &&
|
||||||
|
($info['wavpack']['blockheader']['minor_version'] > 16))) {
|
||||||
|
$info['error'][] = 'Expecting WavPack version between "4.2" and "4.16", found version "'.$info['wavpack']['blockheader']['major_version'].'.'.$info['wavpack']['blockheader']['minor_version'].'" at offset '.$info['wavpack']['blockheader']['offset'];
|
||||||
|
switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') {
|
||||||
|
case 'wavpack':
|
||||||
|
case 'wvc':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['audio']);
|
||||||
|
unset($info['wavpack']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['wavpack']['blockheader']['track_number'] = ord($wavpackheader{10}); // unused
|
||||||
|
$info['wavpack']['blockheader']['index_number'] = ord($wavpackheader{11}); // unused
|
||||||
|
$info['wavpack']['blockheader']['total_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 12, 4));
|
||||||
|
$info['wavpack']['blockheader']['block_index'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 16, 4));
|
||||||
|
$info['wavpack']['blockheader']['block_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 20, 4));
|
||||||
|
$info['wavpack']['blockheader']['flags_raw'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 24, 4));
|
||||||
|
$info['wavpack']['blockheader']['crc'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 28, 4));
|
||||||
|
|
||||||
|
$info['wavpack']['blockheader']['flags']['bytes_per_sample'] = 1 + ($info['wavpack']['blockheader']['flags_raw'] & 0x00000003);
|
||||||
|
$info['wavpack']['blockheader']['flags']['mono'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000004);
|
||||||
|
$info['wavpack']['blockheader']['flags']['hybrid'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000008);
|
||||||
|
$info['wavpack']['blockheader']['flags']['joint_stereo'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000010);
|
||||||
|
$info['wavpack']['blockheader']['flags']['cross_decorrelation'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000020);
|
||||||
|
$info['wavpack']['blockheader']['flags']['hybrid_noiseshape'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000040);
|
||||||
|
$info['wavpack']['blockheader']['flags']['ieee_32bit_float'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000080);
|
||||||
|
$info['wavpack']['blockheader']['flags']['int_32bit'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000100);
|
||||||
|
$info['wavpack']['blockheader']['flags']['hybrid_bitrate_noise'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000200);
|
||||||
|
$info['wavpack']['blockheader']['flags']['hybrid_balance_noise'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000400);
|
||||||
|
$info['wavpack']['blockheader']['flags']['multichannel_initial'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000800);
|
||||||
|
$info['wavpack']['blockheader']['flags']['multichannel_final'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00001000);
|
||||||
|
|
||||||
|
$info['audio']['lossless'] = !$info['wavpack']['blockheader']['flags']['hybrid'];
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!feof($this->getid3->fp) && (ftell($this->getid3->fp) < ($blockheader_offset + $blockheader_size + 8))) {
|
||||||
|
|
||||||
|
$metablock = array('offset'=>ftell($this->getid3->fp));
|
||||||
|
$metablockheader = fread($this->getid3->fp, 2);
|
||||||
|
if (feof($this->getid3->fp)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$metablock['id'] = ord($metablockheader{0});
|
||||||
|
$metablock['function_id'] = ($metablock['id'] & 0x3F);
|
||||||
|
$metablock['function_name'] = $this->WavPackMetablockNameLookup($metablock['function_id']);
|
||||||
|
|
||||||
|
// The 0x20 bit in the id of the meta subblocks (which is defined as
|
||||||
|
// ID_OPTIONAL_DATA) is a permanent part of the id. The idea is that
|
||||||
|
// if a decoder encounters an id that it does not know about, it uses
|
||||||
|
// that "ID_OPTIONAL_DATA" flag to determine what to do. If it is set
|
||||||
|
// then the decoder simply ignores the metadata, but if it is zero
|
||||||
|
// then the decoder should quit because it means that an understanding
|
||||||
|
// of the metadata is required to correctly decode the audio.
|
||||||
|
$metablock['non_decoder'] = (bool) ($metablock['id'] & 0x20);
|
||||||
|
|
||||||
|
$metablock['padded_data'] = (bool) ($metablock['id'] & 0x40);
|
||||||
|
$metablock['large_block'] = (bool) ($metablock['id'] & 0x80);
|
||||||
|
if ($metablock['large_block']) {
|
||||||
|
$metablockheader .= fread($this->getid3->fp, 2);
|
||||||
|
}
|
||||||
|
$metablock['size'] = getid3_lib::LittleEndian2Int(substr($metablockheader, 1)) * 2; // size is stored in words
|
||||||
|
$metablock['data'] = null;
|
||||||
|
|
||||||
|
if ($metablock['size'] > 0) {
|
||||||
|
|
||||||
|
switch ($metablock['function_id']) {
|
||||||
|
case 0x21: // ID_RIFF_HEADER
|
||||||
|
case 0x22: // ID_RIFF_TRAILER
|
||||||
|
case 0x23: // ID_REPLAY_GAIN
|
||||||
|
case 0x24: // ID_CUESHEET
|
||||||
|
case 0x25: // ID_CONFIG_BLOCK
|
||||||
|
case 0x26: // ID_MD5_CHECKSUM
|
||||||
|
$metablock['data'] = fread($this->getid3->fp, $metablock['size']);
|
||||||
|
|
||||||
|
if ($metablock['padded_data']) {
|
||||||
|
// padded to the nearest even byte
|
||||||
|
$metablock['size']--;
|
||||||
|
$metablock['data'] = substr($metablock['data'], 0, -1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x00: // ID_DUMMY
|
||||||
|
case 0x01: // ID_ENCODER_INFO
|
||||||
|
case 0x02: // ID_DECORR_TERMS
|
||||||
|
case 0x03: // ID_DECORR_WEIGHTS
|
||||||
|
case 0x04: // ID_DECORR_SAMPLES
|
||||||
|
case 0x05: // ID_ENTROPY_VARS
|
||||||
|
case 0x06: // ID_HYBRID_PROFILE
|
||||||
|
case 0x07: // ID_SHAPING_WEIGHTS
|
||||||
|
case 0x08: // ID_FLOAT_INFO
|
||||||
|
case 0x09: // ID_INT32_INFO
|
||||||
|
case 0x0A: // ID_WV_BITSTREAM
|
||||||
|
case 0x0B: // ID_WVC_BITSTREAM
|
||||||
|
case 0x0C: // ID_WVX_BITSTREAM
|
||||||
|
case 0x0D: // ID_CHANNEL_INFO
|
||||||
|
fseek($this->getid3->fp, $metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size'], SEEK_SET);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['warning'][] = 'Unexpected metablock type "0x'.str_pad(dechex($metablock['function_id']), 2, '0', STR_PAD_LEFT).'" at offset '.$metablock['offset'];
|
||||||
|
fseek($this->getid3->fp, $metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size'], SEEK_SET);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($metablock['function_id']) {
|
||||||
|
case 0x21: // ID_RIFF_HEADER
|
||||||
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||||
|
$original_wav_filesize = getid3_lib::LittleEndian2Int(substr($metablock['data'], 4, 4));
|
||||||
|
|
||||||
|
$getid3_temp = new getID3();
|
||||||
|
$getid3_temp->openfile($this->getid3->filename);
|
||||||
|
$getid3_riff = new getid3_riff($getid3_temp);
|
||||||
|
$getid3_riff->ParseRIFFdata($metablock['data']);
|
||||||
|
$metablock['riff'] = $getid3_temp->info['riff'];
|
||||||
|
$info['audio']['sample_rate'] = $getid3_temp->info['riff']['raw']['fmt ']['nSamplesPerSec'];
|
||||||
|
unset($getid3_riff, $getid3_temp);
|
||||||
|
|
||||||
|
$metablock['riff']['original_filesize'] = $original_wav_filesize;
|
||||||
|
$info['wavpack']['riff_trailer_size'] = $original_wav_filesize - $metablock['riff']['WAVE']['data'][0]['size'] - $metablock['riff']['header_size'];
|
||||||
|
$info['playtime_seconds'] = $info['wavpack']['blockheader']['total_samples'] / $info['audio']['sample_rate'];
|
||||||
|
|
||||||
|
// Safe RIFF header in case there's a RIFF footer later
|
||||||
|
$metablockRIFFheader = $metablock['data'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 0x22: // ID_RIFF_TRAILER
|
||||||
|
$metablockRIFFfooter = $metablockRIFFheader.$metablock['data'];
|
||||||
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
|
||||||
|
|
||||||
|
$startoffset = $metablock['offset'] + ($metablock['large_block'] ? 4 : 2);
|
||||||
|
$getid3_temp = new getID3();
|
||||||
|
$getid3_temp->openfile($this->getid3->filename);
|
||||||
|
$getid3_temp->info['avdataend'] = $info['avdataend'];
|
||||||
|
$getid3_temp->info['fileformat'] = 'riff';
|
||||||
|
$getid3_riff = new getid3_riff($getid3_temp);
|
||||||
|
$metablock['riff'] = $getid3_riff->ParseRIFF($startoffset, $startoffset + $metablock['size']);
|
||||||
|
|
||||||
|
if (!empty($metablock['riff']['INFO'])) {
|
||||||
|
$getid3_riff->RIFFcommentsParse($metablock['riff']['INFO'], $metablock['comments']);
|
||||||
|
$info['tags']['riff'] = $metablock['comments'];
|
||||||
|
}
|
||||||
|
unset($getid3_temp, $getid3_riff);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 0x23: // ID_REPLAY_GAIN
|
||||||
|
$info['warning'][] = 'WavPack "Replay Gain" contents not yet handled by getID3() in metablock at offset '.$metablock['offset'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 0x24: // ID_CUESHEET
|
||||||
|
$info['warning'][] = 'WavPack "Cuesheet" contents not yet handled by getID3() in metablock at offset '.$metablock['offset'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 0x25: // ID_CONFIG_BLOCK
|
||||||
|
$metablock['flags_raw'] = getid3_lib::LittleEndian2Int(substr($metablock['data'], 0, 3));
|
||||||
|
|
||||||
|
$metablock['flags']['adobe_mode'] = (bool) ($metablock['flags_raw'] & 0x000001); // "adobe" mode for 32-bit floats
|
||||||
|
$metablock['flags']['fast_flag'] = (bool) ($metablock['flags_raw'] & 0x000002); // fast mode
|
||||||
|
$metablock['flags']['very_fast_flag'] = (bool) ($metablock['flags_raw'] & 0x000004); // double fast
|
||||||
|
$metablock['flags']['high_flag'] = (bool) ($metablock['flags_raw'] & 0x000008); // high quality mode
|
||||||
|
$metablock['flags']['very_high_flag'] = (bool) ($metablock['flags_raw'] & 0x000010); // double high (not used yet)
|
||||||
|
$metablock['flags']['bitrate_kbps'] = (bool) ($metablock['flags_raw'] & 0x000020); // bitrate is kbps, not bits / sample
|
||||||
|
$metablock['flags']['auto_shaping'] = (bool) ($metablock['flags_raw'] & 0x000040); // automatic noise shaping
|
||||||
|
$metablock['flags']['shape_override'] = (bool) ($metablock['flags_raw'] & 0x000080); // shaping mode specified
|
||||||
|
$metablock['flags']['joint_override'] = (bool) ($metablock['flags_raw'] & 0x000100); // joint-stereo mode specified
|
||||||
|
$metablock['flags']['copy_time'] = (bool) ($metablock['flags_raw'] & 0x000200); // copy file-time from source
|
||||||
|
$metablock['flags']['create_exe'] = (bool) ($metablock['flags_raw'] & 0x000400); // create executable
|
||||||
|
$metablock['flags']['create_wvc'] = (bool) ($metablock['flags_raw'] & 0x000800); // create correction file
|
||||||
|
$metablock['flags']['optimize_wvc'] = (bool) ($metablock['flags_raw'] & 0x001000); // maximize bybrid compression
|
||||||
|
$metablock['flags']['quality_mode'] = (bool) ($metablock['flags_raw'] & 0x002000); // psychoacoustic quality mode
|
||||||
|
$metablock['flags']['raw_flag'] = (bool) ($metablock['flags_raw'] & 0x004000); // raw mode (not implemented yet)
|
||||||
|
$metablock['flags']['calc_noise'] = (bool) ($metablock['flags_raw'] & 0x008000); // calc noise in hybrid mode
|
||||||
|
$metablock['flags']['lossy_mode'] = (bool) ($metablock['flags_raw'] & 0x010000); // obsolete (for information)
|
||||||
|
$metablock['flags']['extra_mode'] = (bool) ($metablock['flags_raw'] & 0x020000); // extra processing mode
|
||||||
|
$metablock['flags']['skip_wvx'] = (bool) ($metablock['flags_raw'] & 0x040000); // no wvx stream w/ floats & big ints
|
||||||
|
$metablock['flags']['md5_checksum'] = (bool) ($metablock['flags_raw'] & 0x080000); // compute & store MD5 signature
|
||||||
|
$metablock['flags']['quiet_mode'] = (bool) ($metablock['flags_raw'] & 0x100000); // don't report progress %
|
||||||
|
|
||||||
|
$info['wavpack']['config_flags'] = $metablock['flags'];
|
||||||
|
|
||||||
|
|
||||||
|
$info['audio']['encoder_options'] = '';
|
||||||
|
if ($info['wavpack']['blockheader']['flags']['hybrid']) {
|
||||||
|
$info['audio']['encoder_options'] .= ' -b???';
|
||||||
|
}
|
||||||
|
$info['audio']['encoder_options'] .= ($metablock['flags']['adobe_mode'] ? ' -a' : '');
|
||||||
|
$info['audio']['encoder_options'] .= ($metablock['flags']['optimize_wvc'] ? ' -cc' : '');
|
||||||
|
$info['audio']['encoder_options'] .= ($metablock['flags']['create_exe'] ? ' -e' : '');
|
||||||
|
$info['audio']['encoder_options'] .= ($metablock['flags']['fast_flag'] ? ' -f' : '');
|
||||||
|
$info['audio']['encoder_options'] .= ($metablock['flags']['joint_override'] ? ' -j?' : '');
|
||||||
|
$info['audio']['encoder_options'] .= ($metablock['flags']['high_flag'] ? ' -h' : '');
|
||||||
|
$info['audio']['encoder_options'] .= ($metablock['flags']['md5_checksum'] ? ' -m' : '');
|
||||||
|
$info['audio']['encoder_options'] .= ($metablock['flags']['calc_noise'] ? ' -n' : '');
|
||||||
|
$info['audio']['encoder_options'] .= ($metablock['flags']['shape_override'] ? ' -s?' : '');
|
||||||
|
$info['audio']['encoder_options'] .= ($metablock['flags']['extra_mode'] ? ' -x?' : '');
|
||||||
|
if (!empty($info['audio']['encoder_options'])) {
|
||||||
|
$info['audio']['encoder_options'] = trim($info['audio']['encoder_options']);
|
||||||
|
} elseif (isset($info['audio']['encoder_options'])) {
|
||||||
|
unset($info['audio']['encoder_options']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 0x26: // ID_MD5_CHECKSUM
|
||||||
|
if (strlen($metablock['data']) == 16) {
|
||||||
|
$info['md5_data_source'] = strtolower(getid3_lib::PrintHexBytes($metablock['data'], true, false, false));
|
||||||
|
} else {
|
||||||
|
$info['warning'][] = 'Expecting 16 bytes of WavPack "MD5 Checksum" in metablock at offset '.$metablock['offset'].', but found '.strlen($metablock['data']).' bytes';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 0x00: // ID_DUMMY
|
||||||
|
case 0x01: // ID_ENCODER_INFO
|
||||||
|
case 0x02: // ID_DECORR_TERMS
|
||||||
|
case 0x03: // ID_DECORR_WEIGHTS
|
||||||
|
case 0x04: // ID_DECORR_SAMPLES
|
||||||
|
case 0x05: // ID_ENTROPY_VARS
|
||||||
|
case 0x06: // ID_HYBRID_PROFILE
|
||||||
|
case 0x07: // ID_SHAPING_WEIGHTS
|
||||||
|
case 0x08: // ID_FLOAT_INFO
|
||||||
|
case 0x09: // ID_INT32_INFO
|
||||||
|
case 0x0A: // ID_WV_BITSTREAM
|
||||||
|
case 0x0B: // ID_WVC_BITSTREAM
|
||||||
|
case 0x0C: // ID_WVX_BITSTREAM
|
||||||
|
case 0x0D: // ID_CHANNEL_INFO
|
||||||
|
unset($metablock);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!empty($metablock)) {
|
||||||
|
$info['wavpack']['metablocks'][] = $metablock;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['audio']['encoder'] = 'WavPack v'.$info['wavpack']['blockheader']['major_version'].'.'.str_pad($info['wavpack']['blockheader']['minor_version'], 2, '0', STR_PAD_LEFT);
|
||||||
|
$info['audio']['bits_per_sample'] = $info['wavpack']['blockheader']['flags']['bytes_per_sample'] * 8;
|
||||||
|
$info['audio']['channels'] = ($info['wavpack']['blockheader']['flags']['mono'] ? 1 : 2);
|
||||||
|
|
||||||
|
if (!empty($info['playtime_seconds'])) {
|
||||||
|
|
||||||
|
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['audio']['dataformat'] = 'wvc';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function WavPackMetablockNameLookup(&$id) {
|
||||||
|
static $WavPackMetablockNameLookup = array(
|
||||||
|
0x00 => 'Dummy',
|
||||||
|
0x01 => 'Encoder Info',
|
||||||
|
0x02 => 'Decorrelation Terms',
|
||||||
|
0x03 => 'Decorrelation Weights',
|
||||||
|
0x04 => 'Decorrelation Samples',
|
||||||
|
0x05 => 'Entropy Variables',
|
||||||
|
0x06 => 'Hybrid Profile',
|
||||||
|
0x07 => 'Shaping Weights',
|
||||||
|
0x08 => 'Float Info',
|
||||||
|
0x09 => 'Int32 Info',
|
||||||
|
0x0A => 'WV Bitstream',
|
||||||
|
0x0B => 'WVC Bitstream',
|
||||||
|
0x0C => 'WVX Bitstream',
|
||||||
|
0x0D => 'Channel Info',
|
||||||
|
0x21 => 'RIFF header',
|
||||||
|
0x22 => 'RIFF trailer',
|
||||||
|
0x23 => 'Replay Gain',
|
||||||
|
0x24 => 'Cuesheet',
|
||||||
|
0x25 => 'Config Block',
|
||||||
|
0x26 => 'MD5 Checksum',
|
||||||
|
);
|
||||||
|
return (isset($WavPackMetablockNameLookup[$id]) ? $WavPackMetablockNameLookup[$id] : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
690
app/library/getid3/module.graphic.bmp.php
Normal file
690
app/library/getid3/module.graphic.bmp.php
Normal file
|
@ -0,0 +1,690 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.graphic.bmp.php //
|
||||||
|
// module for analyzing BMP Image files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_bmp extends getid3_handler
|
||||||
|
{
|
||||||
|
var $ExtractPalette = false;
|
||||||
|
var $ExtractData = false;
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
// shortcuts
|
||||||
|
$info['bmp']['header']['raw'] = array();
|
||||||
|
$thisfile_bmp = &$info['bmp'];
|
||||||
|
$thisfile_bmp_header = &$thisfile_bmp['header'];
|
||||||
|
$thisfile_bmp_header_raw = &$thisfile_bmp_header['raw'];
|
||||||
|
|
||||||
|
// BITMAPFILEHEADER [14 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_62uq.asp
|
||||||
|
// all versions
|
||||||
|
// WORD bfType;
|
||||||
|
// DWORD bfSize;
|
||||||
|
// WORD bfReserved1;
|
||||||
|
// WORD bfReserved2;
|
||||||
|
// DWORD bfOffBits;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$offset = 0;
|
||||||
|
$BMPheader = fread($this->getid3->fp, 14 + 40);
|
||||||
|
|
||||||
|
$thisfile_bmp_header_raw['identifier'] = substr($BMPheader, $offset, 2);
|
||||||
|
$offset += 2;
|
||||||
|
|
||||||
|
$magic = 'BM';
|
||||||
|
if ($thisfile_bmp_header_raw['identifier'] != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_bmp_header_raw['identifier']).'"';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['bmp']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_bmp_header_raw['filesize'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['reserved1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_bmp_header_raw['reserved2'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_bmp_header_raw['data_offset'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['header_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
|
||||||
|
// check if the hardcoded-to-1 "planes" is at offset 22 or 26
|
||||||
|
$planes22 = getid3_lib::LittleEndian2Int(substr($BMPheader, 22, 2));
|
||||||
|
$planes26 = getid3_lib::LittleEndian2Int(substr($BMPheader, 26, 2));
|
||||||
|
if (($planes22 == 1) && ($planes26 != 1)) {
|
||||||
|
$thisfile_bmp['type_os'] = 'OS/2';
|
||||||
|
$thisfile_bmp['type_version'] = 1;
|
||||||
|
} elseif (($planes26 == 1) && ($planes22 != 1)) {
|
||||||
|
$thisfile_bmp['type_os'] = 'Windows';
|
||||||
|
$thisfile_bmp['type_version'] = 1;
|
||||||
|
} elseif ($thisfile_bmp_header_raw['header_size'] == 12) {
|
||||||
|
$thisfile_bmp['type_os'] = 'OS/2';
|
||||||
|
$thisfile_bmp['type_version'] = 1;
|
||||||
|
} elseif ($thisfile_bmp_header_raw['header_size'] == 40) {
|
||||||
|
$thisfile_bmp['type_os'] = 'Windows';
|
||||||
|
$thisfile_bmp['type_version'] = 1;
|
||||||
|
} elseif ($thisfile_bmp_header_raw['header_size'] == 84) {
|
||||||
|
$thisfile_bmp['type_os'] = 'Windows';
|
||||||
|
$thisfile_bmp['type_version'] = 4;
|
||||||
|
} elseif ($thisfile_bmp_header_raw['header_size'] == 100) {
|
||||||
|
$thisfile_bmp['type_os'] = 'Windows';
|
||||||
|
$thisfile_bmp['type_version'] = 5;
|
||||||
|
} else {
|
||||||
|
$info['error'][] = 'Unknown BMP subtype (or not a BMP file)';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['bmp']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['fileformat'] = 'bmp';
|
||||||
|
$info['video']['dataformat'] = 'bmp';
|
||||||
|
$info['video']['lossless'] = true;
|
||||||
|
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||||
|
|
||||||
|
if ($thisfile_bmp['type_os'] == 'OS/2') {
|
||||||
|
|
||||||
|
// OS/2-format BMP
|
||||||
|
// http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm
|
||||||
|
|
||||||
|
// DWORD Size; /* Size of this structure in bytes */
|
||||||
|
// DWORD Width; /* Bitmap width in pixels */
|
||||||
|
// DWORD Height; /* Bitmap height in pixel */
|
||||||
|
// WORD NumPlanes; /* Number of bit planes (color depth) */
|
||||||
|
// WORD BitsPerPixel; /* Number of bits per pixel per plane */
|
||||||
|
|
||||||
|
$thisfile_bmp_header_raw['width'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_bmp_header_raw['height'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_bmp_header_raw['planes'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_bmp_header_raw['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
|
||||||
|
$info['video']['resolution_x'] = $thisfile_bmp_header_raw['width'];
|
||||||
|
$info['video']['resolution_y'] = $thisfile_bmp_header_raw['height'];
|
||||||
|
$info['video']['codec'] = 'BI_RGB '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
|
||||||
|
$info['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel'];
|
||||||
|
|
||||||
|
if ($thisfile_bmp['type_version'] >= 2) {
|
||||||
|
// DWORD Compression; /* Bitmap compression scheme */
|
||||||
|
// DWORD ImageDataSize; /* Size of bitmap data in bytes */
|
||||||
|
// DWORD XResolution; /* X resolution of display device */
|
||||||
|
// DWORD YResolution; /* Y resolution of display device */
|
||||||
|
// DWORD ColorsUsed; /* Number of color table indices used */
|
||||||
|
// DWORD ColorsImportant; /* Number of important color indices */
|
||||||
|
// WORD Units; /* Type of units used to measure resolution */
|
||||||
|
// WORD Reserved; /* Pad structure to 4-byte boundary */
|
||||||
|
// WORD Recording; /* Recording algorithm */
|
||||||
|
// WORD Rendering; /* Halftoning algorithm used */
|
||||||
|
// DWORD Size1; /* Reserved for halftoning algorithm use */
|
||||||
|
// DWORD Size2; /* Reserved for halftoning algorithm use */
|
||||||
|
// DWORD ColorEncoding; /* Color model used in bitmap */
|
||||||
|
// DWORD Identifier; /* Reserved for application use */
|
||||||
|
|
||||||
|
$thisfile_bmp_header_raw['compression'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['bmp_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['resolution_h'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['resolution_v'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['colors_used'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['colors_important'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['resolution_units'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_bmp_header_raw['reserved1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_bmp_header_raw['recording'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_bmp_header_raw['rendering'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_bmp_header_raw['size1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['size2'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['color_encoding'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['identifier'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$thisfile_bmp_header['compression'] = $this->BMPcompressionOS2Lookup($thisfile_bmp_header_raw['compression']);
|
||||||
|
|
||||||
|
$info['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
|
||||||
|
}
|
||||||
|
|
||||||
|
} elseif ($thisfile_bmp['type_os'] == 'Windows') {
|
||||||
|
|
||||||
|
// Windows-format BMP
|
||||||
|
|
||||||
|
// BITMAPINFOHEADER - [40 bytes] http://msdn.microsoft.com/library/en-us/gdi/bitmaps_1rw2.asp
|
||||||
|
// all versions
|
||||||
|
// DWORD biSize;
|
||||||
|
// LONG biWidth;
|
||||||
|
// LONG biHeight;
|
||||||
|
// WORD biPlanes;
|
||||||
|
// WORD biBitCount;
|
||||||
|
// DWORD biCompression;
|
||||||
|
// DWORD biSizeImage;
|
||||||
|
// LONG biXPelsPerMeter;
|
||||||
|
// LONG biYPelsPerMeter;
|
||||||
|
// DWORD biClrUsed;
|
||||||
|
// DWORD biClrImportant;
|
||||||
|
|
||||||
|
// possibly integrate this section and module.audio-video.riff.php::ParseBITMAPINFOHEADER() ?
|
||||||
|
|
||||||
|
$thisfile_bmp_header_raw['width'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['height'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['planes'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_bmp_header_raw['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_bmp_header_raw['compression'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['bmp_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['resolution_h'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['resolution_v'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['colors_used'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['colors_important'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$thisfile_bmp_header['compression'] = $this->BMPcompressionWindowsLookup($thisfile_bmp_header_raw['compression']);
|
||||||
|
$info['video']['resolution_x'] = $thisfile_bmp_header_raw['width'];
|
||||||
|
$info['video']['resolution_y'] = $thisfile_bmp_header_raw['height'];
|
||||||
|
$info['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
|
||||||
|
$info['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel'];
|
||||||
|
|
||||||
|
if (($thisfile_bmp['type_version'] >= 4) || ($thisfile_bmp_header_raw['compression'] == 3)) {
|
||||||
|
// should only be v4+, but BMPs with type_version==1 and BI_BITFIELDS compression have been seen
|
||||||
|
$BMPheader .= fread($this->getid3->fp, 44);
|
||||||
|
|
||||||
|
// BITMAPV4HEADER - [44 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_2k1e.asp
|
||||||
|
// Win95+, WinNT4.0+
|
||||||
|
// DWORD bV4RedMask;
|
||||||
|
// DWORD bV4GreenMask;
|
||||||
|
// DWORD bV4BlueMask;
|
||||||
|
// DWORD bV4AlphaMask;
|
||||||
|
// DWORD bV4CSType;
|
||||||
|
// CIEXYZTRIPLE bV4Endpoints;
|
||||||
|
// DWORD bV4GammaRed;
|
||||||
|
// DWORD bV4GammaGreen;
|
||||||
|
// DWORD bV4GammaBlue;
|
||||||
|
$thisfile_bmp_header_raw['red_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['green_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['blue_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['alpha_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['cs_type'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['ciexyz_red'] = substr($BMPheader, $offset, 4);
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['ciexyz_green'] = substr($BMPheader, $offset, 4);
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['ciexyz_blue'] = substr($BMPheader, $offset, 4);
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['gamma_red'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['gamma_green'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['gamma_blue'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$thisfile_bmp_header['ciexyz_red'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_red']));
|
||||||
|
$thisfile_bmp_header['ciexyz_green'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_green']));
|
||||||
|
$thisfile_bmp_header['ciexyz_blue'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_blue']));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($thisfile_bmp['type_version'] >= 5) {
|
||||||
|
$BMPheader .= fread($this->getid3->fp, 16);
|
||||||
|
|
||||||
|
// BITMAPV5HEADER - [16 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_7c36.asp
|
||||||
|
// Win98+, Win2000+
|
||||||
|
// DWORD bV5Intent;
|
||||||
|
// DWORD bV5ProfileData;
|
||||||
|
// DWORD bV5ProfileSize;
|
||||||
|
// DWORD bV5Reserved;
|
||||||
|
$thisfile_bmp_header_raw['intent'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['profile_data_offset'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['profile_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_bmp_header_raw['reserved3'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['error'][] = 'Unknown BMP format in header.';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($this->ExtractPalette || $this->ExtractData) {
|
||||||
|
$PaletteEntries = 0;
|
||||||
|
if ($thisfile_bmp_header_raw['bits_per_pixel'] < 16) {
|
||||||
|
$PaletteEntries = pow(2, $thisfile_bmp_header_raw['bits_per_pixel']);
|
||||||
|
} elseif (isset($thisfile_bmp_header_raw['colors_used']) && ($thisfile_bmp_header_raw['colors_used'] > 0) && ($thisfile_bmp_header_raw['colors_used'] <= 256)) {
|
||||||
|
$PaletteEntries = $thisfile_bmp_header_raw['colors_used'];
|
||||||
|
}
|
||||||
|
if ($PaletteEntries > 0) {
|
||||||
|
$BMPpalette = fread($this->getid3->fp, 4 * $PaletteEntries);
|
||||||
|
$paletteoffset = 0;
|
||||||
|
for ($i = 0; $i < $PaletteEntries; $i++) {
|
||||||
|
// RGBQUAD - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_5f8y.asp
|
||||||
|
// BYTE rgbBlue;
|
||||||
|
// BYTE rgbGreen;
|
||||||
|
// BYTE rgbRed;
|
||||||
|
// BYTE rgbReserved;
|
||||||
|
$blue = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
|
||||||
|
$green = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
|
||||||
|
$red = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
|
||||||
|
if (($thisfile_bmp['type_os'] == 'OS/2') && ($thisfile_bmp['type_version'] == 1)) {
|
||||||
|
// no padding byte
|
||||||
|
} else {
|
||||||
|
$paletteoffset++; // padding byte
|
||||||
|
}
|
||||||
|
$thisfile_bmp['palette'][$i] = (($red << 16) | ($green << 8) | $blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->ExtractData) {
|
||||||
|
fseek($this->getid3->fp, $thisfile_bmp_header_raw['data_offset'], SEEK_SET);
|
||||||
|
$RowByteLength = ceil(($thisfile_bmp_header_raw['width'] * ($thisfile_bmp_header_raw['bits_per_pixel'] / 8)) / 4) * 4; // round up to nearest DWORD boundry
|
||||||
|
$BMPpixelData = fread($this->getid3->fp, $thisfile_bmp_header_raw['height'] * $RowByteLength);
|
||||||
|
$pixeldataoffset = 0;
|
||||||
|
$thisfile_bmp_header_raw['compression'] = (isset($thisfile_bmp_header_raw['compression']) ? $thisfile_bmp_header_raw['compression'] : '');
|
||||||
|
switch ($thisfile_bmp_header_raw['compression']) {
|
||||||
|
|
||||||
|
case 0: // BI_RGB
|
||||||
|
switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
|
||||||
|
case 1:
|
||||||
|
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
|
||||||
|
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
|
||||||
|
$paletteindexbyte = ord($BMPpixelData{$pixeldataoffset++});
|
||||||
|
for ($i = 7; $i >= 0; $i--) {
|
||||||
|
$paletteindex = ($paletteindexbyte & (0x01 << $i)) >> $i;
|
||||||
|
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
|
||||||
|
$col++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (($pixeldataoffset % 4) != 0) {
|
||||||
|
// lines are padded to nearest DWORD
|
||||||
|
$pixeldataoffset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
|
||||||
|
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
|
||||||
|
$paletteindexbyte = ord($BMPpixelData{$pixeldataoffset++});
|
||||||
|
for ($i = 1; $i >= 0; $i--) {
|
||||||
|
$paletteindex = ($paletteindexbyte & (0x0F << (4 * $i))) >> (4 * $i);
|
||||||
|
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
|
||||||
|
$col++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (($pixeldataoffset % 4) != 0) {
|
||||||
|
// lines are padded to nearest DWORD
|
||||||
|
$pixeldataoffset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
|
||||||
|
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
|
||||||
|
$paletteindex = ord($BMPpixelData{$pixeldataoffset++});
|
||||||
|
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
|
||||||
|
}
|
||||||
|
while (($pixeldataoffset % 4) != 0) {
|
||||||
|
// lines are padded to nearest DWORD
|
||||||
|
$pixeldataoffset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 24:
|
||||||
|
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
|
||||||
|
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
|
||||||
|
$thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData{$pixeldataoffset+2}) << 16) | (ord($BMPpixelData{$pixeldataoffset+1}) << 8) | ord($BMPpixelData{$pixeldataoffset});
|
||||||
|
$pixeldataoffset += 3;
|
||||||
|
}
|
||||||
|
while (($pixeldataoffset % 4) != 0) {
|
||||||
|
// lines are padded to nearest DWORD
|
||||||
|
$pixeldataoffset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 32:
|
||||||
|
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
|
||||||
|
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
|
||||||
|
$thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData{$pixeldataoffset+3}) << 24) | (ord($BMPpixelData{$pixeldataoffset+2}) << 16) | (ord($BMPpixelData{$pixeldataoffset+1}) << 8) | ord($BMPpixelData{$pixeldataoffset});
|
||||||
|
$pixeldataoffset += 4;
|
||||||
|
}
|
||||||
|
while (($pixeldataoffset % 4) != 0) {
|
||||||
|
// lines are padded to nearest DWORD
|
||||||
|
$pixeldataoffset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 16:
|
||||||
|
// ?
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 1: // BI_RLE8 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
|
||||||
|
switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
|
||||||
|
case 8:
|
||||||
|
$pixelcounter = 0;
|
||||||
|
while ($pixeldataoffset < strlen($BMPpixelData)) {
|
||||||
|
$firstbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||||
|
$secondbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||||
|
if ($firstbyte == 0) {
|
||||||
|
|
||||||
|
// escaped/absolute mode - the first byte of the pair can be set to zero to
|
||||||
|
// indicate an escape character that denotes the end of a line, the end of
|
||||||
|
// a bitmap, or a delta, depending on the value of the second byte.
|
||||||
|
switch ($secondbyte) {
|
||||||
|
case 0:
|
||||||
|
// end of line
|
||||||
|
// no need for special processing, just ignore
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
// end of bitmap
|
||||||
|
$pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
// delta - The 2 bytes following the escape contain unsigned values
|
||||||
|
// indicating the horizontal and vertical offsets of the next pixel
|
||||||
|
// from the current position.
|
||||||
|
$colincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||||
|
$rowincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||||
|
$col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
|
||||||
|
$row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
|
||||||
|
$pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// In absolute mode, the first byte is zero and the second byte is a
|
||||||
|
// value in the range 03H through FFH. The second byte represents the
|
||||||
|
// number of bytes that follow, each of which contains the color index
|
||||||
|
// of a single pixel. Each run must be aligned on a word boundary.
|
||||||
|
for ($i = 0; $i < $secondbyte; $i++) {
|
||||||
|
$paletteindex = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||||
|
$col = $pixelcounter % $thisfile_bmp_header_raw['width'];
|
||||||
|
$row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
|
||||||
|
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
|
||||||
|
$pixelcounter++;
|
||||||
|
}
|
||||||
|
while (($pixeldataoffset % 2) != 0) {
|
||||||
|
// Each run must be aligned on a word boundary.
|
||||||
|
$pixeldataoffset++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// encoded mode - the first byte specifies the number of consecutive pixels
|
||||||
|
// to be drawn using the color index contained in the second byte.
|
||||||
|
for ($i = 0; $i < $firstbyte; $i++) {
|
||||||
|
$col = $pixelcounter % $thisfile_bmp_header_raw['width'];
|
||||||
|
$row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
|
||||||
|
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$secondbyte];
|
||||||
|
$pixelcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
case 2: // BI_RLE4 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
|
||||||
|
switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
|
||||||
|
case 4:
|
||||||
|
$pixelcounter = 0;
|
||||||
|
while ($pixeldataoffset < strlen($BMPpixelData)) {
|
||||||
|
$firstbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||||
|
$secondbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||||
|
if ($firstbyte == 0) {
|
||||||
|
|
||||||
|
// escaped/absolute mode - the first byte of the pair can be set to zero to
|
||||||
|
// indicate an escape character that denotes the end of a line, the end of
|
||||||
|
// a bitmap, or a delta, depending on the value of the second byte.
|
||||||
|
switch ($secondbyte) {
|
||||||
|
case 0:
|
||||||
|
// end of line
|
||||||
|
// no need for special processing, just ignore
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
// end of bitmap
|
||||||
|
$pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
// delta - The 2 bytes following the escape contain unsigned values
|
||||||
|
// indicating the horizontal and vertical offsets of the next pixel
|
||||||
|
// from the current position.
|
||||||
|
$colincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||||
|
$rowincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||||
|
$col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
|
||||||
|
$row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
|
||||||
|
$pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// In absolute mode, the first byte is zero. The second byte contains the number
|
||||||
|
// of color indexes that follow. Subsequent bytes contain color indexes in their
|
||||||
|
// high- and low-order 4 bits, one color index for each pixel. In absolute mode,
|
||||||
|
// each run must be aligned on a word boundary.
|
||||||
|
unset($paletteindexes);
|
||||||
|
for ($i = 0; $i < ceil($secondbyte / 2); $i++) {
|
||||||
|
$paletteindexbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
|
||||||
|
$paletteindexes[] = ($paletteindexbyte & 0xF0) >> 4;
|
||||||
|
$paletteindexes[] = ($paletteindexbyte & 0x0F);
|
||||||
|
}
|
||||||
|
while (($pixeldataoffset % 2) != 0) {
|
||||||
|
// Each run must be aligned on a word boundary.
|
||||||
|
$pixeldataoffset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($paletteindexes as $paletteindex) {
|
||||||
|
$col = $pixelcounter % $thisfile_bmp_header_raw['width'];
|
||||||
|
$row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
|
||||||
|
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
|
||||||
|
$pixelcounter++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// encoded mode - the first byte of the pair contains the number of pixels to be
|
||||||
|
// drawn using the color indexes in the second byte. The second byte contains two
|
||||||
|
// color indexes, one in its high-order 4 bits and one in its low-order 4 bits.
|
||||||
|
// The first of the pixels is drawn using the color specified by the high-order
|
||||||
|
// 4 bits, the second is drawn using the color in the low-order 4 bits, the third
|
||||||
|
// is drawn using the color in the high-order 4 bits, and so on, until all the
|
||||||
|
// pixels specified by the first byte have been drawn.
|
||||||
|
$paletteindexes[0] = ($secondbyte & 0xF0) >> 4;
|
||||||
|
$paletteindexes[1] = ($secondbyte & 0x0F);
|
||||||
|
for ($i = 0; $i < $firstbyte; $i++) {
|
||||||
|
$col = $pixelcounter % $thisfile_bmp_header_raw['width'];
|
||||||
|
$row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
|
||||||
|
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindexes[($i % 2)]];
|
||||||
|
$pixelcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 3: // BI_BITFIELDS
|
||||||
|
switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
|
||||||
|
case 16:
|
||||||
|
case 32:
|
||||||
|
$redshift = 0;
|
||||||
|
$greenshift = 0;
|
||||||
|
$blueshift = 0;
|
||||||
|
while ((($thisfile_bmp_header_raw['red_mask'] >> $redshift) & 0x01) == 0) {
|
||||||
|
$redshift++;
|
||||||
|
}
|
||||||
|
while ((($thisfile_bmp_header_raw['green_mask'] >> $greenshift) & 0x01) == 0) {
|
||||||
|
$greenshift++;
|
||||||
|
}
|
||||||
|
while ((($thisfile_bmp_header_raw['blue_mask'] >> $blueshift) & 0x01) == 0) {
|
||||||
|
$blueshift++;
|
||||||
|
}
|
||||||
|
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
|
||||||
|
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
|
||||||
|
$pixelvalue = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset, $thisfile_bmp_header_raw['bits_per_pixel'] / 8));
|
||||||
|
$pixeldataoffset += $thisfile_bmp_header_raw['bits_per_pixel'] / 8;
|
||||||
|
|
||||||
|
$red = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['red_mask']) >> $redshift) / ($thisfile_bmp_header_raw['red_mask'] >> $redshift)) * 255));
|
||||||
|
$green = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['green_mask']) >> $greenshift) / ($thisfile_bmp_header_raw['green_mask'] >> $greenshift)) * 255));
|
||||||
|
$blue = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['blue_mask']) >> $blueshift) / ($thisfile_bmp_header_raw['blue_mask'] >> $blueshift)) * 255));
|
||||||
|
$thisfile_bmp['data'][$row][$col] = (($red << 16) | ($green << 8) | ($blue));
|
||||||
|
}
|
||||||
|
while (($pixeldataoffset % 4) != 0) {
|
||||||
|
// lines are padded to nearest DWORD
|
||||||
|
$pixeldataoffset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
default: // unhandled compression type
|
||||||
|
$info['error'][] = 'Unknown/unhandled compression type value ('.$thisfile_bmp_header_raw['compression'].') - cannot decompress pixel data';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function PlotBMP(&$BMPinfo) {
|
||||||
|
$starttime = time();
|
||||||
|
if (!isset($BMPinfo['bmp']['data']) || !is_array($BMPinfo['bmp']['data'])) {
|
||||||
|
echo 'ERROR: no pixel data<BR>';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
set_time_limit(intval(round($BMPinfo['resolution_x'] * $BMPinfo['resolution_y'] / 10000)));
|
||||||
|
if ($im = ImageCreateTrueColor($BMPinfo['resolution_x'], $BMPinfo['resolution_y'])) {
|
||||||
|
for ($row = 0; $row < $BMPinfo['resolution_y']; $row++) {
|
||||||
|
for ($col = 0; $col < $BMPinfo['resolution_x']; $col++) {
|
||||||
|
if (isset($BMPinfo['bmp']['data'][$row][$col])) {
|
||||||
|
$red = ($BMPinfo['bmp']['data'][$row][$col] & 0x00FF0000) >> 16;
|
||||||
|
$green = ($BMPinfo['bmp']['data'][$row][$col] & 0x0000FF00) >> 8;
|
||||||
|
$blue = ($BMPinfo['bmp']['data'][$row][$col] & 0x000000FF);
|
||||||
|
$pixelcolor = ImageColorAllocate($im, $red, $green, $blue);
|
||||||
|
ImageSetPixel($im, $col, $row, $pixelcolor);
|
||||||
|
} else {
|
||||||
|
//echo 'ERROR: no data for pixel '.$row.' x '.$col.'<BR>';
|
||||||
|
//return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (headers_sent()) {
|
||||||
|
echo 'plotted '.($BMPinfo['resolution_x'] * $BMPinfo['resolution_y']).' pixels in '.(time() - $starttime).' seconds<BR>';
|
||||||
|
ImageDestroy($im);
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
header('Content-type: image/png');
|
||||||
|
ImagePNG($im);
|
||||||
|
ImageDestroy($im);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function BMPcompressionWindowsLookup($compressionid) {
|
||||||
|
static $BMPcompressionWindowsLookup = array(
|
||||||
|
0 => 'BI_RGB',
|
||||||
|
1 => 'BI_RLE8',
|
||||||
|
2 => 'BI_RLE4',
|
||||||
|
3 => 'BI_BITFIELDS',
|
||||||
|
4 => 'BI_JPEG',
|
||||||
|
5 => 'BI_PNG'
|
||||||
|
);
|
||||||
|
return (isset($BMPcompressionWindowsLookup[$compressionid]) ? $BMPcompressionWindowsLookup[$compressionid] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function BMPcompressionOS2Lookup($compressionid) {
|
||||||
|
static $BMPcompressionOS2Lookup = array(
|
||||||
|
0 => 'BI_RGB',
|
||||||
|
1 => 'BI_RLE8',
|
||||||
|
2 => 'BI_RLE4',
|
||||||
|
3 => 'Huffman 1D',
|
||||||
|
4 => 'BI_RLE24',
|
||||||
|
);
|
||||||
|
return (isset($BMPcompressionOS2Lookup[$compressionid]) ? $BMPcompressionOS2Lookup[$compressionid] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
53
app/library/getid3/module.graphic.efax.php
Normal file
53
app/library/getid3/module.graphic.efax.php
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.archive.efax.php //
|
||||||
|
// module for analyzing eFax files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_efax extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$efaxheader = fread($this->getid3->fp, 1024);
|
||||||
|
|
||||||
|
$info['efax']['header']['magic'] = substr($efaxheader, 0, 2);
|
||||||
|
if ($info['efax']['header']['magic'] != "\xDC\xFE") {
|
||||||
|
$info['error'][] = 'Invalid eFax byte order identifier (expecting DC FE, found '.getid3_lib::PrintHexBytes($info['efax']['header']['magic']).') at offset '.$info['avdataoffset'];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['fileformat'] = 'efax';
|
||||||
|
|
||||||
|
$info['efax']['header']['filesize'] = getid3_lib::LittleEndian2Int(substr($efaxheader, 2, 4));
|
||||||
|
if ($info['efax']['header']['filesize'] != $info['filesize']) {
|
||||||
|
$info['error'][] = 'Probable '.(($info['efax']['header']['filesize'] > $info['filesize']) ? 'truncated' : 'corrupt').' file, expecting '.$info['efax']['header']['filesize'].' bytes, found '.$info['filesize'].' bytes';
|
||||||
|
}
|
||||||
|
$info['efax']['header']['software1'] = rtrim(substr($efaxheader, 26, 32), "\x00");
|
||||||
|
$info['efax']['header']['software2'] = rtrim(substr($efaxheader, 58, 32), "\x00");
|
||||||
|
$info['efax']['header']['software3'] = rtrim(substr($efaxheader, 90, 32), "\x00");
|
||||||
|
|
||||||
|
$info['efax']['header']['pages'] = getid3_lib::LittleEndian2Int(substr($efaxheader, 198, 2));
|
||||||
|
$info['efax']['header']['data_bytes'] = getid3_lib::LittleEndian2Int(substr($efaxheader, 202, 4));
|
||||||
|
|
||||||
|
$info['error'][] = 'eFax parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
184
app/library/getid3/module.graphic.gif.php
Normal file
184
app/library/getid3/module.graphic.gif.php
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.graphic.gif.php //
|
||||||
|
// module for analyzing GIF Image files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_gif extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'gif';
|
||||||
|
$info['video']['dataformat'] = 'gif';
|
||||||
|
$info['video']['lossless'] = true;
|
||||||
|
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$GIFheader = fread($this->getid3->fp, 13);
|
||||||
|
$offset = 0;
|
||||||
|
|
||||||
|
$info['gif']['header']['raw']['identifier'] = substr($GIFheader, $offset, 3);
|
||||||
|
$offset += 3;
|
||||||
|
|
||||||
|
$magic = 'GIF';
|
||||||
|
if ($info['gif']['header']['raw']['identifier'] != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['gif']['header']['raw']['identifier']).'"';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['gif']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['gif']['header']['raw']['version'] = substr($GIFheader, $offset, 3);
|
||||||
|
$offset += 3;
|
||||||
|
$info['gif']['header']['raw']['width'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$info['gif']['header']['raw']['height'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$info['gif']['header']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
$info['gif']['header']['raw']['bg_color_index'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
$info['gif']['header']['raw']['aspect_ratio'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
|
||||||
|
$info['video']['resolution_x'] = $info['gif']['header']['raw']['width'];
|
||||||
|
$info['video']['resolution_y'] = $info['gif']['header']['raw']['height'];
|
||||||
|
$info['gif']['version'] = $info['gif']['header']['raw']['version'];
|
||||||
|
$info['gif']['header']['flags']['global_color_table'] = (bool) ($info['gif']['header']['raw']['flags'] & 0x80);
|
||||||
|
if ($info['gif']['header']['raw']['flags'] & 0x80) {
|
||||||
|
// Number of bits per primary color available to the original image, minus 1
|
||||||
|
$info['gif']['header']['bits_per_pixel'] = 3 * ((($info['gif']['header']['raw']['flags'] & 0x70) >> 4) + 1);
|
||||||
|
} else {
|
||||||
|
$info['gif']['header']['bits_per_pixel'] = 0;
|
||||||
|
}
|
||||||
|
$info['gif']['header']['flags']['global_color_sorted'] = (bool) ($info['gif']['header']['raw']['flags'] & 0x40);
|
||||||
|
if ($info['gif']['header']['flags']['global_color_table']) {
|
||||||
|
// the number of bytes contained in the Global Color Table. To determine that
|
||||||
|
// actual size of the color table, raise 2 to [the value of the field + 1]
|
||||||
|
$info['gif']['header']['global_color_size'] = pow(2, ($info['gif']['header']['raw']['flags'] & 0x07) + 1);
|
||||||
|
$info['video']['bits_per_sample'] = ($info['gif']['header']['raw']['flags'] & 0x07) + 1;
|
||||||
|
} else {
|
||||||
|
$info['gif']['header']['global_color_size'] = 0;
|
||||||
|
}
|
||||||
|
if ($info['gif']['header']['raw']['aspect_ratio'] != 0) {
|
||||||
|
// Aspect Ratio = (Pixel Aspect Ratio + 15) / 64
|
||||||
|
$info['gif']['header']['aspect_ratio'] = ($info['gif']['header']['raw']['aspect_ratio'] + 15) / 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if ($info['gif']['header']['flags']['global_color_table']) {
|
||||||
|
// $GIFcolorTable = fread($this->getid3->fp, 3 * $info['gif']['header']['global_color_size']);
|
||||||
|
// $offset = 0;
|
||||||
|
// for ($i = 0; $i < $info['gif']['header']['global_color_size']; $i++) {
|
||||||
|
// $red = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1));
|
||||||
|
// $green = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1));
|
||||||
|
// $blue = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1));
|
||||||
|
// $info['gif']['global_color_table'][$i] = (($red << 16) | ($green << 8) | ($blue));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Image Descriptor
|
||||||
|
// while (!feof($this->getid3->fp)) {
|
||||||
|
// $NextBlockTest = fread($this->getid3->fp, 1);
|
||||||
|
// switch ($NextBlockTest) {
|
||||||
|
//
|
||||||
|
// case ',': // ',' - Image separator character
|
||||||
|
//
|
||||||
|
// $ImageDescriptorData = $NextBlockTest.fread($this->getid3->fp, 9);
|
||||||
|
// $ImageDescriptor = array();
|
||||||
|
// $ImageDescriptor['image_left'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 1, 2));
|
||||||
|
// $ImageDescriptor['image_top'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 3, 2));
|
||||||
|
// $ImageDescriptor['image_width'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 5, 2));
|
||||||
|
// $ImageDescriptor['image_height'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 7, 2));
|
||||||
|
// $ImageDescriptor['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 9, 1));
|
||||||
|
// $ImageDescriptor['flags']['use_local_color_map'] = (bool) ($ImageDescriptor['flags_raw'] & 0x80);
|
||||||
|
// $ImageDescriptor['flags']['image_interlaced'] = (bool) ($ImageDescriptor['flags_raw'] & 0x40);
|
||||||
|
// $info['gif']['image_descriptor'][] = $ImageDescriptor;
|
||||||
|
//
|
||||||
|
// if ($ImageDescriptor['flags']['use_local_color_map']) {
|
||||||
|
//
|
||||||
|
// $info['warning'][] = 'This version of getID3() cannot parse local color maps for GIFs';
|
||||||
|
// return true;
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//echo 'Start of raster data: '.ftell($this->getid3->fp).'<BR>';
|
||||||
|
// $RasterData = array();
|
||||||
|
// $RasterData['code_size'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1));
|
||||||
|
// $RasterData['block_byte_count'] = getid3_lib::LittleEndian2Int(fread($this->getid3->fp, 1));
|
||||||
|
// $info['gif']['raster_data'][count($info['gif']['image_descriptor']) - 1] = $RasterData;
|
||||||
|
//
|
||||||
|
// $CurrentCodeSize = $RasterData['code_size'] + 1;
|
||||||
|
// for ($i = 0; $i < pow(2, $RasterData['code_size']); $i++) {
|
||||||
|
// $DefaultDataLookupTable[$i] = chr($i);
|
||||||
|
// }
|
||||||
|
// $DefaultDataLookupTable[pow(2, $RasterData['code_size']) + 0] = ''; // Clear Code
|
||||||
|
// $DefaultDataLookupTable[pow(2, $RasterData['code_size']) + 1] = ''; // End Of Image Code
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// $NextValue = $this->GetLSBits($CurrentCodeSize);
|
||||||
|
// echo 'Clear Code: '.$NextValue.'<BR>';
|
||||||
|
//
|
||||||
|
// $NextValue = $this->GetLSBits($CurrentCodeSize);
|
||||||
|
// echo 'First Color: '.$NextValue.'<BR>';
|
||||||
|
//
|
||||||
|
// $Prefix = $NextValue;
|
||||||
|
//$i = 0;
|
||||||
|
// while ($i++ < 20) {
|
||||||
|
// $NextValue = $this->GetLSBits($CurrentCodeSize);
|
||||||
|
// echo $NextValue.'<BR>';
|
||||||
|
// }
|
||||||
|
//return true;
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// case '!':
|
||||||
|
// // GIF Extension Block
|
||||||
|
// $ExtensionBlockData = $NextBlockTest.fread($this->getid3->fp, 2);
|
||||||
|
// $ExtensionBlock = array();
|
||||||
|
// $ExtensionBlock['function_code'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlockData, 1, 1));
|
||||||
|
// $ExtensionBlock['byte_length'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlockData, 2, 1));
|
||||||
|
// $ExtensionBlock['data'] = fread($this->getid3->fp, $ExtensionBlock['byte_length']);
|
||||||
|
// $info['gif']['extension_blocks'][] = $ExtensionBlock;
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// case ';':
|
||||||
|
// $info['gif']['terminator_offset'] = ftell($this->getid3->fp) - 1;
|
||||||
|
// // GIF Terminator
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
// default:
|
||||||
|
// break;
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetLSBits($bits) {
|
||||||
|
static $bitbuffer = '';
|
||||||
|
while (strlen($bitbuffer) < $bits) {
|
||||||
|
$bitbuffer = str_pad(decbin(ord(fread($this->getid3->fp, 1))), 8, '0', STR_PAD_LEFT).$bitbuffer;
|
||||||
|
}
|
||||||
|
$value = bindec(substr($bitbuffer, 0 - $bits));
|
||||||
|
$bitbuffer = substr($bitbuffer, 0, 0 - $bits);
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
338
app/library/getid3/module.graphic.jpg.php
Normal file
338
app/library/getid3/module.graphic.jpg.php
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.graphic.jpg.php //
|
||||||
|
// module for analyzing JPEG Image files //
|
||||||
|
// dependencies: PHP compiled with --enable-exif (optional) //
|
||||||
|
// module.tag.xmp.php (optional) //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_jpg extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'jpg';
|
||||||
|
$info['video']['dataformat'] = 'jpg';
|
||||||
|
$info['video']['lossless'] = false;
|
||||||
|
$info['video']['bits_per_sample'] = 24;
|
||||||
|
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
|
||||||
|
$imageinfo = array();
|
||||||
|
list($width, $height, $type) = getid3_lib::GetDataImageSize(fread($this->getid3->fp, $info['filesize']), $imageinfo);
|
||||||
|
|
||||||
|
|
||||||
|
if (isset($imageinfo['APP13'])) {
|
||||||
|
// http://php.net/iptcparse
|
||||||
|
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
|
||||||
|
$iptc_parsed = iptcparse($imageinfo['APP13']);
|
||||||
|
if (is_array($iptc_parsed)) {
|
||||||
|
foreach ($iptc_parsed as $iptc_key_raw => $iptc_values) {
|
||||||
|
list($iptc_record, $iptc_tagkey) = explode('#', $iptc_key_raw);
|
||||||
|
$iptc_tagkey = intval(ltrim($iptc_tagkey, '0'));
|
||||||
|
foreach ($iptc_values as $key => $value) {
|
||||||
|
$IPTCrecordName = $this->IPTCrecordName($iptc_record);
|
||||||
|
$IPTCrecordTagName = $this->IPTCrecordTagName($iptc_record, $iptc_tagkey);
|
||||||
|
if (isset($info['iptc'][$IPTCrecordName][$IPTCrecordTagName])) {
|
||||||
|
$info['iptc'][$IPTCrecordName][$IPTCrecordTagName][] = $value;
|
||||||
|
} else {
|
||||||
|
$info['iptc'][$IPTCrecordName][$IPTCrecordTagName] = array($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$returnOK = false;
|
||||||
|
switch ($type) {
|
||||||
|
case IMG_JPG:
|
||||||
|
$info['video']['resolution_x'] = $width;
|
||||||
|
$info['video']['resolution_y'] = $height;
|
||||||
|
|
||||||
|
if (isset($imageinfo['APP1'])) {
|
||||||
|
if (function_exists('exif_read_data')) {
|
||||||
|
if (substr($imageinfo['APP1'], 0, 4) == 'Exif') {
|
||||||
|
$info['jpg']['exif'] = @exif_read_data($info['filenamepath'], '', true, false);
|
||||||
|
} else {
|
||||||
|
$info['warning'][] = 'exif_read_data() cannot parse non-EXIF data in APP1 (expected "Exif", found "'.substr($imageinfo['APP1'], 0, 4).'")';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$info['warning'][] = 'EXIF parsing only available when '.(GETID3_OS_ISWINDOWS ? 'php_exif.dll enabled' : 'compiled with --enable-exif');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$returnOK = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$cast_as_appropriate_keys = array('EXIF', 'IFD0', 'THUMBNAIL');
|
||||||
|
foreach ($cast_as_appropriate_keys as $exif_key) {
|
||||||
|
if (isset($info['jpg']['exif'][$exif_key])) {
|
||||||
|
foreach ($info['jpg']['exif'][$exif_key] as $key => $value) {
|
||||||
|
$info['jpg']['exif'][$exif_key][$key] = $this->CastAsAppropriate($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (isset($info['jpg']['exif']['GPS'])) {
|
||||||
|
|
||||||
|
if (isset($info['jpg']['exif']['GPS']['GPSVersion'])) {
|
||||||
|
for ($i = 0; $i < 4; $i++) {
|
||||||
|
$version_subparts[$i] = ord(substr($info['jpg']['exif']['GPS']['GPSVersion'], $i, 1));
|
||||||
|
}
|
||||||
|
$info['jpg']['exif']['GPS']['computed']['version'] = 'v'.implode('.', $version_subparts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($info['jpg']['exif']['GPS']['GPSDateStamp'])) {
|
||||||
|
$explodedGPSDateStamp = explode(':', $info['jpg']['exif']['GPS']['GPSDateStamp']);
|
||||||
|
$computed_time[5] = (isset($explodedGPSDateStamp[0]) ? $explodedGPSDateStamp[0] : '');
|
||||||
|
$computed_time[3] = (isset($explodedGPSDateStamp[1]) ? $explodedGPSDateStamp[1] : '');
|
||||||
|
$computed_time[4] = (isset($explodedGPSDateStamp[2]) ? $explodedGPSDateStamp[2] : '');
|
||||||
|
|
||||||
|
if (function_exists('date_default_timezone_set')) {
|
||||||
|
date_default_timezone_set('UTC');
|
||||||
|
} else {
|
||||||
|
ini_set('date.timezone', 'UTC');
|
||||||
|
}
|
||||||
|
|
||||||
|
$computed_time = array(0=>0, 1=>0, 2=>0, 3=>0, 4=>0, 5=>0);
|
||||||
|
if (isset($info['jpg']['exif']['GPS']['GPSTimeStamp']) && is_array($info['jpg']['exif']['GPS']['GPSTimeStamp'])) {
|
||||||
|
foreach ($info['jpg']['exif']['GPS']['GPSTimeStamp'] as $key => $value) {
|
||||||
|
$computed_time[$key] = getid3_lib::DecimalizeFraction($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$info['jpg']['exif']['GPS']['computed']['timestamp'] = mktime($computed_time[0], $computed_time[1], $computed_time[2], $computed_time[3], $computed_time[4], $computed_time[5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($info['jpg']['exif']['GPS']['GPSLatitude']) && is_array($info['jpg']['exif']['GPS']['GPSLatitude'])) {
|
||||||
|
$direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSLatitudeRef']) && ($info['jpg']['exif']['GPS']['GPSLatitudeRef'] == 'S')) ? -1 : 1);
|
||||||
|
foreach ($info['jpg']['exif']['GPS']['GPSLatitude'] as $key => $value) {
|
||||||
|
$computed_latitude[$key] = getid3_lib::DecimalizeFraction($value);
|
||||||
|
}
|
||||||
|
$info['jpg']['exif']['GPS']['computed']['latitude'] = $direction_multiplier * ($computed_latitude[0] + ($computed_latitude[1] / 60) + ($computed_latitude[2] / 3600));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($info['jpg']['exif']['GPS']['GPSLongitude']) && is_array($info['jpg']['exif']['GPS']['GPSLongitude'])) {
|
||||||
|
$direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSLongitudeRef']) && ($info['jpg']['exif']['GPS']['GPSLongitudeRef'] == 'W')) ? -1 : 1);
|
||||||
|
foreach ($info['jpg']['exif']['GPS']['GPSLongitude'] as $key => $value) {
|
||||||
|
$computed_longitude[$key] = getid3_lib::DecimalizeFraction($value);
|
||||||
|
}
|
||||||
|
$info['jpg']['exif']['GPS']['computed']['longitude'] = $direction_multiplier * ($computed_longitude[0] + ($computed_longitude[1] / 60) + ($computed_longitude[2] / 3600));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($info['jpg']['exif']['GPS']['GPSAltitude'])) {
|
||||||
|
$direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSAltitudeRef']) && ($info['jpg']['exif']['GPS']['GPSAltitudeRef'] === chr(1))) ? -1 : 1);
|
||||||
|
$info['jpg']['exif']['GPS']['computed']['altitude'] = $direction_multiplier * getid3_lib::DecimalizeFraction($info['jpg']['exif']['GPS']['GPSAltitude']);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.xmp.php', __FILE__, false)) {
|
||||||
|
if (isset($info['filenamepath'])) {
|
||||||
|
$image_xmp = new Image_XMP($info['filenamepath']);
|
||||||
|
$xmp_raw = $image_xmp->getAllTags();
|
||||||
|
foreach ($xmp_raw as $key => $value) {
|
||||||
|
list($subsection, $tagname) = explode(':', $key);
|
||||||
|
$info['xmp'][$subsection][$tagname] = $this->CastAsAppropriate($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$returnOK) {
|
||||||
|
unset($info['fileformat']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function CastAsAppropriate($value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
return $value;
|
||||||
|
} elseif (preg_match('#^[0-9]+/[0-9]+$#', $value)) {
|
||||||
|
return getid3_lib::DecimalizeFraction($value);
|
||||||
|
} elseif (preg_match('#^[0-9]+$#', $value)) {
|
||||||
|
return getid3_lib::CastAsInt($value);
|
||||||
|
} elseif (preg_match('#^[0-9\.]+$#', $value)) {
|
||||||
|
return (float) $value;
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function IPTCrecordName($iptc_record) {
|
||||||
|
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
|
||||||
|
static $IPTCrecordName = array();
|
||||||
|
if (empty($IPTCrecordName)) {
|
||||||
|
$IPTCrecordName = array(
|
||||||
|
1 => 'IPTCEnvelope',
|
||||||
|
2 => 'IPTCApplication',
|
||||||
|
3 => 'IPTCNewsPhoto',
|
||||||
|
7 => 'IPTCPreObjectData',
|
||||||
|
8 => 'IPTCObjectData',
|
||||||
|
9 => 'IPTCPostObjectData',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (isset($IPTCrecordName[$iptc_record]) ? $IPTCrecordName[$iptc_record] : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function IPTCrecordTagName($iptc_record, $iptc_tagkey) {
|
||||||
|
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
|
||||||
|
static $IPTCrecordTagName = array();
|
||||||
|
if (empty($IPTCrecordTagName)) {
|
||||||
|
$IPTCrecordTagName = array(
|
||||||
|
1 => array( // IPTC EnvelopeRecord Tags
|
||||||
|
0 => 'EnvelopeRecordVersion',
|
||||||
|
5 => 'Destination',
|
||||||
|
20 => 'FileFormat',
|
||||||
|
22 => 'FileVersion',
|
||||||
|
30 => 'ServiceIdentifier',
|
||||||
|
40 => 'EnvelopeNumber',
|
||||||
|
50 => 'ProductID',
|
||||||
|
60 => 'EnvelopePriority',
|
||||||
|
70 => 'DateSent',
|
||||||
|
80 => 'TimeSent',
|
||||||
|
90 => 'CodedCharacterSet',
|
||||||
|
100 => 'UniqueObjectName',
|
||||||
|
120 => 'ARMIdentifier',
|
||||||
|
122 => 'ARMVersion',
|
||||||
|
),
|
||||||
|
2 => array( // IPTC ApplicationRecord Tags
|
||||||
|
0 => 'ApplicationRecordVersion',
|
||||||
|
3 => 'ObjectTypeReference',
|
||||||
|
4 => 'ObjectAttributeReference',
|
||||||
|
5 => 'ObjectName',
|
||||||
|
7 => 'EditStatus',
|
||||||
|
8 => 'EditorialUpdate',
|
||||||
|
10 => 'Urgency',
|
||||||
|
12 => 'SubjectReference',
|
||||||
|
15 => 'Category',
|
||||||
|
20 => 'SupplementalCategories',
|
||||||
|
22 => 'FixtureIdentifier',
|
||||||
|
25 => 'Keywords',
|
||||||
|
26 => 'ContentLocationCode',
|
||||||
|
27 => 'ContentLocationName',
|
||||||
|
30 => 'ReleaseDate',
|
||||||
|
35 => 'ReleaseTime',
|
||||||
|
37 => 'ExpirationDate',
|
||||||
|
38 => 'ExpirationTime',
|
||||||
|
40 => 'SpecialInstructions',
|
||||||
|
42 => 'ActionAdvised',
|
||||||
|
45 => 'ReferenceService',
|
||||||
|
47 => 'ReferenceDate',
|
||||||
|
50 => 'ReferenceNumber',
|
||||||
|
55 => 'DateCreated',
|
||||||
|
60 => 'TimeCreated',
|
||||||
|
62 => 'DigitalCreationDate',
|
||||||
|
63 => 'DigitalCreationTime',
|
||||||
|
65 => 'OriginatingProgram',
|
||||||
|
70 => 'ProgramVersion',
|
||||||
|
75 => 'ObjectCycle',
|
||||||
|
80 => 'By-line',
|
||||||
|
85 => 'By-lineTitle',
|
||||||
|
90 => 'City',
|
||||||
|
92 => 'Sub-location',
|
||||||
|
95 => 'Province-State',
|
||||||
|
100 => 'Country-PrimaryLocationCode',
|
||||||
|
101 => 'Country-PrimaryLocationName',
|
||||||
|
103 => 'OriginalTransmissionReference',
|
||||||
|
105 => 'Headline',
|
||||||
|
110 => 'Credit',
|
||||||
|
115 => 'Source',
|
||||||
|
116 => 'CopyrightNotice',
|
||||||
|
118 => 'Contact',
|
||||||
|
120 => 'Caption-Abstract',
|
||||||
|
121 => 'LocalCaption',
|
||||||
|
122 => 'Writer-Editor',
|
||||||
|
125 => 'RasterizedCaption',
|
||||||
|
130 => 'ImageType',
|
||||||
|
131 => 'ImageOrientation',
|
||||||
|
135 => 'LanguageIdentifier',
|
||||||
|
150 => 'AudioType',
|
||||||
|
151 => 'AudioSamplingRate',
|
||||||
|
152 => 'AudioSamplingResolution',
|
||||||
|
153 => 'AudioDuration',
|
||||||
|
154 => 'AudioOutcue',
|
||||||
|
184 => 'JobID',
|
||||||
|
185 => 'MasterDocumentID',
|
||||||
|
186 => 'ShortDocumentID',
|
||||||
|
187 => 'UniqueDocumentID',
|
||||||
|
188 => 'OwnerID',
|
||||||
|
200 => 'ObjectPreviewFileFormat',
|
||||||
|
201 => 'ObjectPreviewFileVersion',
|
||||||
|
202 => 'ObjectPreviewData',
|
||||||
|
221 => 'Prefs',
|
||||||
|
225 => 'ClassifyState',
|
||||||
|
228 => 'SimilarityIndex',
|
||||||
|
230 => 'DocumentNotes',
|
||||||
|
231 => 'DocumentHistory',
|
||||||
|
232 => 'ExifCameraInfo',
|
||||||
|
),
|
||||||
|
3 => array( // IPTC NewsPhoto Tags
|
||||||
|
0 => 'NewsPhotoVersion',
|
||||||
|
10 => 'IPTCPictureNumber',
|
||||||
|
20 => 'IPTCImageWidth',
|
||||||
|
30 => 'IPTCImageHeight',
|
||||||
|
40 => 'IPTCPixelWidth',
|
||||||
|
50 => 'IPTCPixelHeight',
|
||||||
|
55 => 'SupplementalType',
|
||||||
|
60 => 'ColorRepresentation',
|
||||||
|
64 => 'InterchangeColorSpace',
|
||||||
|
65 => 'ColorSequence',
|
||||||
|
66 => 'ICC_Profile',
|
||||||
|
70 => 'ColorCalibrationMatrix',
|
||||||
|
80 => 'LookupTable',
|
||||||
|
84 => 'NumIndexEntries',
|
||||||
|
85 => 'ColorPalette',
|
||||||
|
86 => 'IPTCBitsPerSample',
|
||||||
|
90 => 'SampleStructure',
|
||||||
|
100 => 'ScanningDirection',
|
||||||
|
102 => 'IPTCImageRotation',
|
||||||
|
110 => 'DataCompressionMethod',
|
||||||
|
120 => 'QuantizationMethod',
|
||||||
|
125 => 'EndPoints',
|
||||||
|
130 => 'ExcursionTolerance',
|
||||||
|
135 => 'BitsPerComponent',
|
||||||
|
140 => 'MaximumDensityRange',
|
||||||
|
145 => 'GammaCompensatedValue',
|
||||||
|
),
|
||||||
|
7 => array( // IPTC PreObjectData Tags
|
||||||
|
10 => 'SizeMode',
|
||||||
|
20 => 'MaxSubfileSize',
|
||||||
|
90 => 'ObjectSizeAnnounced',
|
||||||
|
95 => 'MaximumObjectSize',
|
||||||
|
),
|
||||||
|
8 => array( // IPTC ObjectData Tags
|
||||||
|
10 => 'SubFile',
|
||||||
|
),
|
||||||
|
9 => array( // IPTC PostObjectData Tags
|
||||||
|
10 => 'ConfirmedObjectSize',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
return (isset($IPTCrecordTagName[$iptc_record][$iptc_tagkey]) ? $IPTCrecordTagName[$iptc_record][$iptc_tagkey] : $iptc_tagkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
134
app/library/getid3/module.graphic.pcd.php
Normal file
134
app/library/getid3/module.graphic.pcd.php
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.graphic.pcd.php //
|
||||||
|
// module for analyzing PhotoCD (PCD) Image files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_pcd extends getid3_handler
|
||||||
|
{
|
||||||
|
var $ExtractData = 0;
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'pcd';
|
||||||
|
$info['video']['dataformat'] = 'pcd';
|
||||||
|
$info['video']['lossless'] = false;
|
||||||
|
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'] + 72, SEEK_SET);
|
||||||
|
|
||||||
|
$PCDflags = fread($this->getid3->fp, 1);
|
||||||
|
$PCDisVertical = ((ord($PCDflags) & 0x01) ? true : false);
|
||||||
|
|
||||||
|
|
||||||
|
if ($PCDisVertical) {
|
||||||
|
$info['video']['resolution_x'] = 3072;
|
||||||
|
$info['video']['resolution_y'] = 2048;
|
||||||
|
} else {
|
||||||
|
$info['video']['resolution_x'] = 2048;
|
||||||
|
$info['video']['resolution_y'] = 3072;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($this->ExtractData > 3) {
|
||||||
|
|
||||||
|
$info['error'][] = 'Cannot extract PSD image data for detail levels above BASE (level-3) because encrypted with Kodak-proprietary compression/encryption.';
|
||||||
|
|
||||||
|
} elseif ($this->ExtractData > 0) {
|
||||||
|
|
||||||
|
$PCD_levels[1] = array( 192, 128, 0x02000); // BASE/16
|
||||||
|
$PCD_levels[2] = array( 384, 256, 0x0B800); // BASE/4
|
||||||
|
$PCD_levels[3] = array( 768, 512, 0x30000); // BASE
|
||||||
|
//$PCD_levels[4] = array(1536, 1024, ??); // BASE*4 - encrypted with Kodak-proprietary compression/encryption
|
||||||
|
//$PCD_levels[5] = array(3072, 2048, ??); // BASE*16 - encrypted with Kodak-proprietary compression/encryption
|
||||||
|
//$PCD_levels[6] = array(6144, 4096, ??); // BASE*64 - encrypted with Kodak-proprietary compression/encryption; PhotoCD-Pro only
|
||||||
|
|
||||||
|
list($PCD_width, $PCD_height, $PCD_dataOffset) = $PCD_levels[3];
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'] + $PCD_dataOffset, SEEK_SET);
|
||||||
|
|
||||||
|
for ($y = 0; $y < $PCD_height; $y += 2) {
|
||||||
|
// The image-data of these subtypes start at the respective offsets of 02000h, 0b800h and 30000h.
|
||||||
|
// To decode the YcbYr to the more usual RGB-code, three lines of data have to be read, each
|
||||||
|
// consisting of ‘w’ bytes, where ‘w’ is the width of the image-subtype. The first ‘w’ bytes and
|
||||||
|
// the first half of the third ‘w’ bytes contain data for the first RGB-line, the second ‘w’ bytes
|
||||||
|
// and the second half of the third ‘w’ bytes contain data for a second RGB-line.
|
||||||
|
|
||||||
|
$PCD_data_Y1 = fread($this->getid3->fp, $PCD_width);
|
||||||
|
$PCD_data_Y2 = fread($this->getid3->fp, $PCD_width);
|
||||||
|
$PCD_data_Cb = fread($this->getid3->fp, intval(round($PCD_width / 2)));
|
||||||
|
$PCD_data_Cr = fread($this->getid3->fp, intval(round($PCD_width / 2)));
|
||||||
|
|
||||||
|
for ($x = 0; $x < $PCD_width; $x++) {
|
||||||
|
if ($PCDisVertical) {
|
||||||
|
$info['pcd']['data'][$PCD_width - $x][$y] = $this->YCbCr2RGB(ord($PCD_data_Y1{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)}));
|
||||||
|
$info['pcd']['data'][$PCD_width - $x][$y + 1] = $this->YCbCr2RGB(ord($PCD_data_Y2{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)}));
|
||||||
|
} else {
|
||||||
|
$info['pcd']['data'][$y][$x] = $this->YCbCr2RGB(ord($PCD_data_Y1{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)}));
|
||||||
|
$info['pcd']['data'][$y + 1][$x] = $this->YCbCr2RGB(ord($PCD_data_Y2{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example for plotting extracted data
|
||||||
|
//getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, true);
|
||||||
|
//if ($PCDisVertical) {
|
||||||
|
// $BMPinfo['resolution_x'] = $PCD_height;
|
||||||
|
// $BMPinfo['resolution_y'] = $PCD_width;
|
||||||
|
//} else {
|
||||||
|
// $BMPinfo['resolution_x'] = $PCD_width;
|
||||||
|
// $BMPinfo['resolution_y'] = $PCD_height;
|
||||||
|
//}
|
||||||
|
//$BMPinfo['bmp']['data'] = $info['pcd']['data'];
|
||||||
|
//getid3_bmp::PlotBMP($BMPinfo);
|
||||||
|
//exit;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function YCbCr2RGB($Y, $Cb, $Cr) {
|
||||||
|
static $YCbCr_constants = array();
|
||||||
|
if (empty($YCbCr_constants)) {
|
||||||
|
$YCbCr_constants['red']['Y'] = 0.0054980 * 256;
|
||||||
|
$YCbCr_constants['red']['Cb'] = 0.0000000 * 256;
|
||||||
|
$YCbCr_constants['red']['Cr'] = 0.0051681 * 256;
|
||||||
|
$YCbCr_constants['green']['Y'] = 0.0054980 * 256;
|
||||||
|
$YCbCr_constants['green']['Cb'] = -0.0015446 * 256;
|
||||||
|
$YCbCr_constants['green']['Cr'] = -0.0026325 * 256;
|
||||||
|
$YCbCr_constants['blue']['Y'] = 0.0054980 * 256;
|
||||||
|
$YCbCr_constants['blue']['Cb'] = 0.0079533 * 256;
|
||||||
|
$YCbCr_constants['blue']['Cr'] = 0.0000000 * 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
$RGBcolor = array('red'=>0, 'green'=>0, 'blue'=>0);
|
||||||
|
foreach ($RGBcolor as $rgbname => $dummy) {
|
||||||
|
$RGBcolor[$rgbname] = max(0,
|
||||||
|
min(255,
|
||||||
|
intval(
|
||||||
|
round(
|
||||||
|
($YCbCr_constants[$rgbname]['Y'] * $Y) +
|
||||||
|
($YCbCr_constants[$rgbname]['Cb'] * ($Cb - 156)) +
|
||||||
|
($YCbCr_constants[$rgbname]['Cr'] * ($Cr - 137))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (($RGBcolor['red'] * 65536) + ($RGBcolor['green'] * 256) + $RGBcolor['blue']);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
520
app/library/getid3/module.graphic.png.php
Normal file
520
app/library/getid3/module.graphic.png.php
Normal file
|
@ -0,0 +1,520 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.graphic.png.php //
|
||||||
|
// module for analyzing PNG Image files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_png extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$info['png'] = array();
|
||||||
|
$thisfile_png = &$info['png'];
|
||||||
|
|
||||||
|
$info['fileformat'] = 'png';
|
||||||
|
$info['video']['dataformat'] = 'png';
|
||||||
|
$info['video']['lossless'] = false;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$PNGfiledata = fread($this->getid3->fp, $this->getid3->fread_buffer_size());
|
||||||
|
$offset = 0;
|
||||||
|
|
||||||
|
$PNGidentifier = substr($PNGfiledata, $offset, 8); // $89 $50 $4E $47 $0D $0A $1A $0A
|
||||||
|
$offset += 8;
|
||||||
|
|
||||||
|
if ($PNGidentifier != "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") {
|
||||||
|
$info['error'][] = 'First 8 bytes of file ('.getid3_lib::PrintHexBytes($PNGidentifier).') did not match expected PNG identifier';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (((ftell($this->getid3->fp) - (strlen($PNGfiledata) - $offset)) < $info['filesize'])) {
|
||||||
|
$chunk['data_length'] = getid3_lib::BigEndian2Int(substr($PNGfiledata, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
while (((strlen($PNGfiledata) - $offset) < ($chunk['data_length'] + 4)) && (ftell($this->getid3->fp) < $info['filesize'])) {
|
||||||
|
$PNGfiledata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size());
|
||||||
|
}
|
||||||
|
$chunk['type_text'] = substr($PNGfiledata, $offset, 4);
|
||||||
|
$offset += 4;
|
||||||
|
$chunk['type_raw'] = getid3_lib::BigEndian2Int($chunk['type_text']);
|
||||||
|
$chunk['data'] = substr($PNGfiledata, $offset, $chunk['data_length']);
|
||||||
|
$offset += $chunk['data_length'];
|
||||||
|
$chunk['crc'] = getid3_lib::BigEndian2Int(substr($PNGfiledata, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
|
||||||
|
$chunk['flags']['ancilliary'] = (bool) ($chunk['type_raw'] & 0x20000000);
|
||||||
|
$chunk['flags']['private'] = (bool) ($chunk['type_raw'] & 0x00200000);
|
||||||
|
$chunk['flags']['reserved'] = (bool) ($chunk['type_raw'] & 0x00002000);
|
||||||
|
$chunk['flags']['safe_to_copy'] = (bool) ($chunk['type_raw'] & 0x00000020);
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$thisfile_png[$chunk['type_text']] = array();
|
||||||
|
$thisfile_png_chunk_type_text = &$thisfile_png[$chunk['type_text']];
|
||||||
|
|
||||||
|
switch ($chunk['type_text']) {
|
||||||
|
|
||||||
|
case 'IHDR': // Image Header
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
$thisfile_png_chunk_type_text['width'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4));
|
||||||
|
$thisfile_png_chunk_type_text['height'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4));
|
||||||
|
$thisfile_png_chunk_type_text['raw']['bit_depth'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 1));
|
||||||
|
$thisfile_png_chunk_type_text['raw']['color_type'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 9, 1));
|
||||||
|
$thisfile_png_chunk_type_text['raw']['compression_method'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 10, 1));
|
||||||
|
$thisfile_png_chunk_type_text['raw']['filter_method'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 11, 1));
|
||||||
|
$thisfile_png_chunk_type_text['raw']['interlace_method'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 12, 1));
|
||||||
|
|
||||||
|
$thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['raw']['compression_method']);
|
||||||
|
$thisfile_png_chunk_type_text['color_type']['palette'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x01);
|
||||||
|
$thisfile_png_chunk_type_text['color_type']['true_color'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x02);
|
||||||
|
$thisfile_png_chunk_type_text['color_type']['alpha'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x04);
|
||||||
|
|
||||||
|
$info['video']['resolution_x'] = $thisfile_png_chunk_type_text['width'];
|
||||||
|
$info['video']['resolution_y'] = $thisfile_png_chunk_type_text['height'];
|
||||||
|
|
||||||
|
$info['video']['bits_per_sample'] = $this->IHDRcalculateBitsPerSample($thisfile_png_chunk_type_text['raw']['color_type'], $thisfile_png_chunk_type_text['raw']['bit_depth']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'PLTE': // Palette
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
$paletteoffset = 0;
|
||||||
|
for ($i = 0; $i <= 255; $i++) {
|
||||||
|
//$thisfile_png_chunk_type_text['red'][$i] = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
|
||||||
|
//$thisfile_png_chunk_type_text['green'][$i] = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
|
||||||
|
//$thisfile_png_chunk_type_text['blue'][$i] = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
|
||||||
|
$red = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
|
||||||
|
$green = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
|
||||||
|
$blue = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
|
||||||
|
$thisfile_png_chunk_type_text[$i] = (($red << 16) | ($green << 8) | ($blue));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'tRNS': // Transparency
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
switch ($thisfile_png['IHDR']['raw']['color_type']) {
|
||||||
|
case 0:
|
||||||
|
$thisfile_png_chunk_type_text['transparent_color_gray'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 2));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
$thisfile_png_chunk_type_text['transparent_color_red'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 2));
|
||||||
|
$thisfile_png_chunk_type_text['transparent_color_green'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 2));
|
||||||
|
$thisfile_png_chunk_type_text['transparent_color_blue'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 2));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
for ($i = 0; $i < strlen($chunk['data']); $i++) {
|
||||||
|
$thisfile_png_chunk_type_text['palette_opacity'][$i] = getid3_lib::BigEndian2Int(substr($chunk['data'], $i, 1));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
case 6:
|
||||||
|
$info['error'][] = 'Invalid color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type'];
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['warning'][] = 'Unhandled color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'gAMA': // Image Gamma
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
$thisfile_png_chunk_type_text['gamma'] = getid3_lib::BigEndian2Int($chunk['data']) / 100000;
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'cHRM': // Primary Chromaticities
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
$thisfile_png_chunk_type_text['white_x'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4)) / 100000;
|
||||||
|
$thisfile_png_chunk_type_text['white_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4)) / 100000;
|
||||||
|
$thisfile_png_chunk_type_text['red_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 4)) / 100000;
|
||||||
|
$thisfile_png_chunk_type_text['red_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 12, 4)) / 100000;
|
||||||
|
$thisfile_png_chunk_type_text['green_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 16, 4)) / 100000;
|
||||||
|
$thisfile_png_chunk_type_text['green_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 20, 4)) / 100000;
|
||||||
|
$thisfile_png_chunk_type_text['blue_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 24, 4)) / 100000;
|
||||||
|
$thisfile_png_chunk_type_text['blue_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 28, 4)) / 100000;
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'sRGB': // Standard RGB Color Space
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
$thisfile_png_chunk_type_text['reindering_intent'] = getid3_lib::BigEndian2Int($chunk['data']);
|
||||||
|
$thisfile_png_chunk_type_text['reindering_intent_text'] = $this->PNGsRGBintentLookup($thisfile_png_chunk_type_text['reindering_intent']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'iCCP': // Embedded ICC Profile
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
list($profilename, $compressiondata) = explode("\x00", $chunk['data'], 2);
|
||||||
|
$thisfile_png_chunk_type_text['profile_name'] = $profilename;
|
||||||
|
$thisfile_png_chunk_type_text['compression_method'] = getid3_lib::BigEndian2Int(substr($compressiondata, 0, 1));
|
||||||
|
$thisfile_png_chunk_type_text['compression_profile'] = substr($compressiondata, 1);
|
||||||
|
|
||||||
|
$thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'tEXt': // Textual Data
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
list($keyword, $text) = explode("\x00", $chunk['data'], 2);
|
||||||
|
$thisfile_png_chunk_type_text['keyword'] = $keyword;
|
||||||
|
$thisfile_png_chunk_type_text['text'] = $text;
|
||||||
|
|
||||||
|
$thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'zTXt': // Compressed Textual Data
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
list($keyword, $otherdata) = explode("\x00", $chunk['data'], 2);
|
||||||
|
$thisfile_png_chunk_type_text['keyword'] = $keyword;
|
||||||
|
$thisfile_png_chunk_type_text['compression_method'] = getid3_lib::BigEndian2Int(substr($otherdata, 0, 1));
|
||||||
|
$thisfile_png_chunk_type_text['compressed_text'] = substr($otherdata, 1);
|
||||||
|
$thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']);
|
||||||
|
switch ($thisfile_png_chunk_type_text['compression_method']) {
|
||||||
|
case 0:
|
||||||
|
$thisfile_png_chunk_type_text['text'] = gzuncompress($thisfile_png_chunk_type_text['compressed_text']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// unknown compression method
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($thisfile_png_chunk_type_text['text'])) {
|
||||||
|
$thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'iTXt': // International Textual Data
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
list($keyword, $otherdata) = explode("\x00", $chunk['data'], 2);
|
||||||
|
$thisfile_png_chunk_type_text['keyword'] = $keyword;
|
||||||
|
$thisfile_png_chunk_type_text['compression'] = (bool) getid3_lib::BigEndian2Int(substr($otherdata, 0, 1));
|
||||||
|
$thisfile_png_chunk_type_text['compression_method'] = getid3_lib::BigEndian2Int(substr($otherdata, 1, 1));
|
||||||
|
$thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']);
|
||||||
|
list($languagetag, $translatedkeyword, $text) = explode("\x00", substr($otherdata, 2), 3);
|
||||||
|
$thisfile_png_chunk_type_text['language_tag'] = $languagetag;
|
||||||
|
$thisfile_png_chunk_type_text['translated_keyword'] = $translatedkeyword;
|
||||||
|
|
||||||
|
if ($thisfile_png_chunk_type_text['compression']) {
|
||||||
|
|
||||||
|
switch ($thisfile_png_chunk_type_text['compression_method']) {
|
||||||
|
case 0:
|
||||||
|
$thisfile_png_chunk_type_text['text'] = gzuncompress($text);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// unknown compression method
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$thisfile_png_chunk_type_text['text'] = $text;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($thisfile_png_chunk_type_text['text'])) {
|
||||||
|
$thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'bKGD': // Background Color
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
switch ($thisfile_png['IHDR']['raw']['color_type']) {
|
||||||
|
case 0:
|
||||||
|
case 4:
|
||||||
|
$thisfile_png_chunk_type_text['background_gray'] = getid3_lib::BigEndian2Int($chunk['data']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
case 6:
|
||||||
|
$thisfile_png_chunk_type_text['background_red'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth']));
|
||||||
|
$thisfile_png_chunk_type_text['background_green'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth']));
|
||||||
|
$thisfile_png_chunk_type_text['background_blue'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth']));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
$thisfile_png_chunk_type_text['background_index'] = getid3_lib::BigEndian2Int($chunk['data']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'pHYs': // Physical Pixel Dimensions
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
$thisfile_png_chunk_type_text['pixels_per_unit_x'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4));
|
||||||
|
$thisfile_png_chunk_type_text['pixels_per_unit_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4));
|
||||||
|
$thisfile_png_chunk_type_text['unit_specifier'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 1));
|
||||||
|
$thisfile_png_chunk_type_text['unit'] = $this->PNGpHYsUnitLookup($thisfile_png_chunk_type_text['unit_specifier']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'sBIT': // Significant Bits
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
switch ($thisfile_png['IHDR']['raw']['color_type']) {
|
||||||
|
case 0:
|
||||||
|
$thisfile_png_chunk_type_text['significant_bits_gray'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
$thisfile_png_chunk_type_text['significant_bits_red'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
|
||||||
|
$thisfile_png_chunk_type_text['significant_bits_green'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1, 1));
|
||||||
|
$thisfile_png_chunk_type_text['significant_bits_blue'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
$thisfile_png_chunk_type_text['significant_bits_gray'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
|
||||||
|
$thisfile_png_chunk_type_text['significant_bits_alpha'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1, 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
$thisfile_png_chunk_type_text['significant_bits_red'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
|
||||||
|
$thisfile_png_chunk_type_text['significant_bits_green'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1, 1));
|
||||||
|
$thisfile_png_chunk_type_text['significant_bits_blue'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 1));
|
||||||
|
$thisfile_png_chunk_type_text['significant_bits_alpha'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 3, 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'sPLT': // Suggested Palette
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
list($palettename, $otherdata) = explode("\x00", $chunk['data'], 2);
|
||||||
|
$thisfile_png_chunk_type_text['palette_name'] = $palettename;
|
||||||
|
$sPLToffset = 0;
|
||||||
|
$thisfile_png_chunk_type_text['sample_depth_bits'] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, 1));
|
||||||
|
$sPLToffset += 1;
|
||||||
|
$thisfile_png_chunk_type_text['sample_depth_bytes'] = $thisfile_png_chunk_type_text['sample_depth_bits'] / 8;
|
||||||
|
$paletteCounter = 0;
|
||||||
|
while ($sPLToffset < strlen($otherdata)) {
|
||||||
|
$thisfile_png_chunk_type_text['red'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes']));
|
||||||
|
$sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes'];
|
||||||
|
$thisfile_png_chunk_type_text['green'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes']));
|
||||||
|
$sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes'];
|
||||||
|
$thisfile_png_chunk_type_text['blue'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes']));
|
||||||
|
$sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes'];
|
||||||
|
$thisfile_png_chunk_type_text['alpha'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes']));
|
||||||
|
$sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes'];
|
||||||
|
$thisfile_png_chunk_type_text['frequency'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, 2));
|
||||||
|
$sPLToffset += 2;
|
||||||
|
$paletteCounter++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'hIST': // Palette Histogram
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
$hISTcounter = 0;
|
||||||
|
while ($hISTcounter < strlen($chunk['data'])) {
|
||||||
|
$thisfile_png_chunk_type_text[$hISTcounter] = getid3_lib::BigEndian2Int(substr($chunk['data'], $hISTcounter / 2, 2));
|
||||||
|
$hISTcounter += 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'tIME': // Image Last-Modification Time
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
$thisfile_png_chunk_type_text['year'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 2));
|
||||||
|
$thisfile_png_chunk_type_text['month'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 1));
|
||||||
|
$thisfile_png_chunk_type_text['day'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 3, 1));
|
||||||
|
$thisfile_png_chunk_type_text['hour'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 1));
|
||||||
|
$thisfile_png_chunk_type_text['minute'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 5, 1));
|
||||||
|
$thisfile_png_chunk_type_text['second'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 6, 1));
|
||||||
|
$thisfile_png_chunk_type_text['unix'] = gmmktime($thisfile_png_chunk_type_text['hour'], $thisfile_png_chunk_type_text['minute'], $thisfile_png_chunk_type_text['second'], $thisfile_png_chunk_type_text['month'], $thisfile_png_chunk_type_text['day'], $thisfile_png_chunk_type_text['year']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'oFFs': // Image Offset
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
$thisfile_png_chunk_type_text['position_x'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4), false, true);
|
||||||
|
$thisfile_png_chunk_type_text['position_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4), false, true);
|
||||||
|
$thisfile_png_chunk_type_text['unit_specifier'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 1));
|
||||||
|
$thisfile_png_chunk_type_text['unit'] = $this->PNGoFFsUnitLookup($thisfile_png_chunk_type_text['unit_specifier']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'pCAL': // Calibration Of Pixel Values
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
list($calibrationname, $otherdata) = explode("\x00", $chunk['data'], 2);
|
||||||
|
$thisfile_png_chunk_type_text['calibration_name'] = $calibrationname;
|
||||||
|
$pCALoffset = 0;
|
||||||
|
$thisfile_png_chunk_type_text['original_zero'] = getid3_lib::BigEndian2Int(substr($chunk['data'], $pCALoffset, 4), false, true);
|
||||||
|
$pCALoffset += 4;
|
||||||
|
$thisfile_png_chunk_type_text['original_max'] = getid3_lib::BigEndian2Int(substr($chunk['data'], $pCALoffset, 4), false, true);
|
||||||
|
$pCALoffset += 4;
|
||||||
|
$thisfile_png_chunk_type_text['equation_type'] = getid3_lib::BigEndian2Int(substr($chunk['data'], $pCALoffset, 1));
|
||||||
|
$pCALoffset += 1;
|
||||||
|
$thisfile_png_chunk_type_text['equation_type_text'] = $this->PNGpCALequationTypeLookup($thisfile_png_chunk_type_text['equation_type']);
|
||||||
|
$thisfile_png_chunk_type_text['parameter_count'] = getid3_lib::BigEndian2Int(substr($chunk['data'], $pCALoffset, 1));
|
||||||
|
$pCALoffset += 1;
|
||||||
|
$thisfile_png_chunk_type_text['parameters'] = explode("\x00", substr($chunk['data'], $pCALoffset));
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'sCAL': // Physical Scale Of Image Subject
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
$thisfile_png_chunk_type_text['unit_specifier'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
|
||||||
|
$thisfile_png_chunk_type_text['unit'] = $this->PNGsCALUnitLookup($thisfile_png_chunk_type_text['unit_specifier']);
|
||||||
|
list($pixelwidth, $pixelheight) = explode("\x00", substr($chunk['data'], 1));
|
||||||
|
$thisfile_png_chunk_type_text['pixel_width'] = $pixelwidth;
|
||||||
|
$thisfile_png_chunk_type_text['pixel_height'] = $pixelheight;
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'gIFg': // GIF Graphic Control Extension
|
||||||
|
$gIFgCounter = 0;
|
||||||
|
if (isset($thisfile_png_chunk_type_text) && is_array($thisfile_png_chunk_type_text)) {
|
||||||
|
$gIFgCounter = count($thisfile_png_chunk_type_text);
|
||||||
|
}
|
||||||
|
$thisfile_png_chunk_type_text[$gIFgCounter]['header'] = $chunk;
|
||||||
|
$thisfile_png_chunk_type_text[$gIFgCounter]['disposal_method'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
|
||||||
|
$thisfile_png_chunk_type_text[$gIFgCounter]['user_input_flag'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1, 1));
|
||||||
|
$thisfile_png_chunk_type_text[$gIFgCounter]['delay_time'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 2));
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'gIFx': // GIF Application Extension
|
||||||
|
$gIFxCounter = 0;
|
||||||
|
if (isset($thisfile_png_chunk_type_text) && is_array($thisfile_png_chunk_type_text)) {
|
||||||
|
$gIFxCounter = count($thisfile_png_chunk_type_text);
|
||||||
|
}
|
||||||
|
$thisfile_png_chunk_type_text[$gIFxCounter]['header'] = $chunk;
|
||||||
|
$thisfile_png_chunk_type_text[$gIFxCounter]['application_identifier'] = substr($chunk['data'], 0, 8);
|
||||||
|
$thisfile_png_chunk_type_text[$gIFxCounter]['authentication_code'] = substr($chunk['data'], 8, 3);
|
||||||
|
$thisfile_png_chunk_type_text[$gIFxCounter]['application_data'] = substr($chunk['data'], 11);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'IDAT': // Image Data
|
||||||
|
$idatinformationfieldindex = 0;
|
||||||
|
if (isset($thisfile_png['IDAT']) && is_array($thisfile_png['IDAT'])) {
|
||||||
|
$idatinformationfieldindex = count($thisfile_png['IDAT']);
|
||||||
|
}
|
||||||
|
unset($chunk['data']);
|
||||||
|
$thisfile_png_chunk_type_text[$idatinformationfieldindex]['header'] = $chunk;
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case 'IEND': // Image Trailer
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
//unset($chunk['data']);
|
||||||
|
$thisfile_png_chunk_type_text['header'] = $chunk;
|
||||||
|
$info['warning'][] = 'Unhandled chunk type: '.$chunk['type_text'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PNGsRGBintentLookup($sRGB) {
|
||||||
|
static $PNGsRGBintentLookup = array(
|
||||||
|
0 => 'Perceptual',
|
||||||
|
1 => 'Relative colorimetric',
|
||||||
|
2 => 'Saturation',
|
||||||
|
3 => 'Absolute colorimetric'
|
||||||
|
);
|
||||||
|
return (isset($PNGsRGBintentLookup[$sRGB]) ? $PNGsRGBintentLookup[$sRGB] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function PNGcompressionMethodLookup($compressionmethod) {
|
||||||
|
static $PNGcompressionMethodLookup = array(
|
||||||
|
0 => 'deflate/inflate'
|
||||||
|
);
|
||||||
|
return (isset($PNGcompressionMethodLookup[$compressionmethod]) ? $PNGcompressionMethodLookup[$compressionmethod] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function PNGpHYsUnitLookup($unitid) {
|
||||||
|
static $PNGpHYsUnitLookup = array(
|
||||||
|
0 => 'unknown',
|
||||||
|
1 => 'meter'
|
||||||
|
);
|
||||||
|
return (isset($PNGpHYsUnitLookup[$unitid]) ? $PNGpHYsUnitLookup[$unitid] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function PNGoFFsUnitLookup($unitid) {
|
||||||
|
static $PNGoFFsUnitLookup = array(
|
||||||
|
0 => 'pixel',
|
||||||
|
1 => 'micrometer'
|
||||||
|
);
|
||||||
|
return (isset($PNGoFFsUnitLookup[$unitid]) ? $PNGoFFsUnitLookup[$unitid] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function PNGpCALequationTypeLookup($equationtype) {
|
||||||
|
static $PNGpCALequationTypeLookup = array(
|
||||||
|
0 => 'Linear mapping',
|
||||||
|
1 => 'Base-e exponential mapping',
|
||||||
|
2 => 'Arbitrary-base exponential mapping',
|
||||||
|
3 => 'Hyperbolic mapping'
|
||||||
|
);
|
||||||
|
return (isset($PNGpCALequationTypeLookup[$equationtype]) ? $PNGpCALequationTypeLookup[$equationtype] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function PNGsCALUnitLookup($unitid) {
|
||||||
|
static $PNGsCALUnitLookup = array(
|
||||||
|
0 => 'meter',
|
||||||
|
1 => 'radian'
|
||||||
|
);
|
||||||
|
return (isset($PNGsCALUnitLookup[$unitid]) ? $PNGsCALUnitLookup[$unitid] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function IHDRcalculateBitsPerSample($color_type, $bit_depth) {
|
||||||
|
switch ($color_type) {
|
||||||
|
case 0: // Each pixel is a grayscale sample.
|
||||||
|
return $bit_depth;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // Each pixel is an R,G,B triple
|
||||||
|
return 3 * $bit_depth;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: // Each pixel is a palette index; a PLTE chunk must appear.
|
||||||
|
return $bit_depth;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4: // Each pixel is a grayscale sample, followed by an alpha sample.
|
||||||
|
return 2 * $bit_depth;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6: // Each pixel is an R,G,B triple, followed by an alpha sample.
|
||||||
|
return 4 * $bit_depth;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
104
app/library/getid3/module.graphic.svg.php
Normal file
104
app/library/getid3/module.graphic.svg.php
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.graphic.svg.php //
|
||||||
|
// module for analyzing SVG Image files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_svg extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
|
||||||
|
$SVGheader = fread($this->getid3->fp, 4096);
|
||||||
|
if (preg_match('#\<\?xml([^\>]+)\?\>#i', $SVGheader, $matches)) {
|
||||||
|
$info['svg']['xml']['raw'] = $matches;
|
||||||
|
}
|
||||||
|
if (preg_match('#\<\!DOCTYPE([^\>]+)\>#i', $SVGheader, $matches)) {
|
||||||
|
$info['svg']['doctype']['raw'] = $matches;
|
||||||
|
}
|
||||||
|
if (preg_match('#\<svg([^\>]+)\>#i', $SVGheader, $matches)) {
|
||||||
|
$info['svg']['svg']['raw'] = $matches;
|
||||||
|
}
|
||||||
|
if (isset($info['svg']['svg']['raw'])) {
|
||||||
|
|
||||||
|
$sections_to_fix = array('xml', 'doctype', 'svg');
|
||||||
|
foreach ($sections_to_fix as $section_to_fix) {
|
||||||
|
if (!isset($info['svg'][$section_to_fix])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$section_data = array();
|
||||||
|
while (preg_match('/ "([^"]+)"/', $info['svg'][$section_to_fix]['raw'][1], $matches)) {
|
||||||
|
$section_data[] = $matches[1];
|
||||||
|
$info['svg'][$section_to_fix]['raw'][1] = str_replace($matches[0], '', $info['svg'][$section_to_fix]['raw'][1]);
|
||||||
|
}
|
||||||
|
while (preg_match('/([^\s]+)="([^"]+)"/', $info['svg'][$section_to_fix]['raw'][1], $matches)) {
|
||||||
|
$section_data[] = $matches[0];
|
||||||
|
$info['svg'][$section_to_fix]['raw'][1] = str_replace($matches[0], '', $info['svg'][$section_to_fix]['raw'][1]);
|
||||||
|
}
|
||||||
|
$section_data = array_merge($section_data, preg_split('/[\s,]+/', $info['svg'][$section_to_fix]['raw'][1]));
|
||||||
|
foreach ($section_data as $keyvaluepair) {
|
||||||
|
$keyvaluepair = trim($keyvaluepair);
|
||||||
|
if ($keyvaluepair) {
|
||||||
|
$keyvalueexploded = explode('=', $keyvaluepair);
|
||||||
|
$key = (isset($keyvalueexploded[0]) ? $keyvalueexploded[0] : '');
|
||||||
|
$value = (isset($keyvalueexploded[1]) ? $keyvalueexploded[1] : '');
|
||||||
|
$info['svg'][$section_to_fix]['sections'][$key] = trim($value, '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['fileformat'] = 'svg';
|
||||||
|
$info['video']['dataformat'] = 'svg';
|
||||||
|
$info['video']['lossless'] = true;
|
||||||
|
//$info['video']['bits_per_sample'] = 24;
|
||||||
|
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||||
|
|
||||||
|
if (!empty($info['svg']['svg']['sections']['width'])) {
|
||||||
|
$info['svg']['width'] = intval($info['svg']['svg']['sections']['width']);
|
||||||
|
}
|
||||||
|
if (!empty($info['svg']['svg']['sections']['height'])) {
|
||||||
|
$info['svg']['height'] = intval($info['svg']['svg']['sections']['height']);
|
||||||
|
}
|
||||||
|
if (!empty($info['svg']['svg']['sections']['version'])) {
|
||||||
|
$info['svg']['version'] = $info['svg']['svg']['sections']['version'];
|
||||||
|
}
|
||||||
|
if (!isset($info['svg']['version']) && isset($info['svg']['doctype']['sections'])) {
|
||||||
|
foreach ($info['svg']['doctype']['sections'] as $key => $value) {
|
||||||
|
if (preg_match('#//W3C//DTD SVG ([0-9\.]+)//#i', $key, $matches)) {
|
||||||
|
$info['svg']['version'] = $matches[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($info['svg']['width'])) {
|
||||||
|
$info['video']['resolution_x'] = $info['svg']['width'];
|
||||||
|
}
|
||||||
|
if (!empty($info['svg']['height'])) {
|
||||||
|
$info['video']['resolution_y'] = $info['svg']['height'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$info['error'][] = 'Did not find expected <svg> tag';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
227
app/library/getid3/module.graphic.tiff.php
Normal file
227
app/library/getid3/module.graphic.tiff.php
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.archive.tiff.php //
|
||||||
|
// module for analyzing TIFF files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_tiff extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$TIFFheader = fread($this->getid3->fp, 4);
|
||||||
|
|
||||||
|
switch (substr($TIFFheader, 0, 2)) {
|
||||||
|
case 'II':
|
||||||
|
$info['tiff']['byte_order'] = 'Intel';
|
||||||
|
break;
|
||||||
|
case 'MM':
|
||||||
|
$info['tiff']['byte_order'] = 'Motorola';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$info['error'][] = 'Invalid TIFF byte order identifier ('.substr($TIFFheader, 0, 2).') at offset '.$info['avdataoffset'];
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['fileformat'] = 'tiff';
|
||||||
|
$info['video']['dataformat'] = 'tiff';
|
||||||
|
$info['video']['lossless'] = true;
|
||||||
|
$info['tiff']['ifd'] = array();
|
||||||
|
$CurrentIFD = array();
|
||||||
|
|
||||||
|
$FieldTypeByteLength = array(1=>1, 2=>1, 3=>2, 4=>4, 5=>8);
|
||||||
|
|
||||||
|
$nextIFDoffset = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']);
|
||||||
|
|
||||||
|
while ($nextIFDoffset > 0) {
|
||||||
|
|
||||||
|
$CurrentIFD['offset'] = $nextIFDoffset;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'] + $nextIFDoffset, SEEK_SET);
|
||||||
|
$CurrentIFD['fieldcount'] = $this->TIFFendian2Int(fread($this->getid3->fp, 2), $info['tiff']['byte_order']);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $CurrentIFD['fieldcount']; $i++) {
|
||||||
|
$CurrentIFD['fields'][$i]['raw']['tag'] = $this->TIFFendian2Int(fread($this->getid3->fp, 2), $info['tiff']['byte_order']);
|
||||||
|
$CurrentIFD['fields'][$i]['raw']['type'] = $this->TIFFendian2Int(fread($this->getid3->fp, 2), $info['tiff']['byte_order']);
|
||||||
|
$CurrentIFD['fields'][$i]['raw']['length'] = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']);
|
||||||
|
$CurrentIFD['fields'][$i]['raw']['offset'] = fread($this->getid3->fp, 4);
|
||||||
|
|
||||||
|
switch ($CurrentIFD['fields'][$i]['raw']['type']) {
|
||||||
|
case 1: // BYTE An 8-bit unsigned integer.
|
||||||
|
if ($CurrentIFD['fields'][$i]['raw']['length'] <= 4) {
|
||||||
|
$CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int(substr($CurrentIFD['fields'][$i]['raw']['offset'], 0, 1), $info['tiff']['byte_order']);
|
||||||
|
} else {
|
||||||
|
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2: // ASCII 8-bit bytes that store ASCII codes; the last byte must be null.
|
||||||
|
if ($CurrentIFD['fields'][$i]['raw']['length'] <= 4) {
|
||||||
|
$CurrentIFD['fields'][$i]['value'] = substr($CurrentIFD['fields'][$i]['raw']['offset'], 3);
|
||||||
|
} else {
|
||||||
|
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: // SHORT A 16-bit (2-byte) unsigned integer.
|
||||||
|
if ($CurrentIFD['fields'][$i]['raw']['length'] <= 2) {
|
||||||
|
$CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int(substr($CurrentIFD['fields'][$i]['raw']['offset'], 0, 2), $info['tiff']['byte_order']);
|
||||||
|
} else {
|
||||||
|
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4: // LONG A 32-bit (4-byte) unsigned integer.
|
||||||
|
if ($CurrentIFD['fields'][$i]['raw']['length'] <= 1) {
|
||||||
|
$CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']);
|
||||||
|
} else {
|
||||||
|
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $info['tiff']['byte_order']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5: // RATIONAL Two LONG_s: the first represents the numerator of a fraction, the second the denominator.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['tiff']['ifd'][] = $CurrentIFD;
|
||||||
|
$CurrentIFD = array();
|
||||||
|
$nextIFDoffset = $this->TIFFendian2Int(fread($this->getid3->fp, 4), $info['tiff']['byte_order']);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($info['tiff']['ifd'] as $IFDid => $IFDarray) {
|
||||||
|
foreach ($IFDarray['fields'] as $key => $fieldarray) {
|
||||||
|
switch ($fieldarray['raw']['tag']) {
|
||||||
|
case 256: // ImageWidth
|
||||||
|
case 257: // ImageLength
|
||||||
|
case 258: // BitsPerSample
|
||||||
|
case 259: // Compression
|
||||||
|
if (!isset($fieldarray['value'])) {
|
||||||
|
fseek($this->getid3->fp, $fieldarray['offset'], SEEK_SET);
|
||||||
|
$info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($this->getid3->fp, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]);
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 270: // ImageDescription
|
||||||
|
case 271: // Make
|
||||||
|
case 272: // Model
|
||||||
|
case 305: // Software
|
||||||
|
case 306: // DateTime
|
||||||
|
case 315: // Artist
|
||||||
|
case 316: // HostComputer
|
||||||
|
if (isset($fieldarray['value'])) {
|
||||||
|
$info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $fieldarray['value'];
|
||||||
|
} else {
|
||||||
|
fseek($this->getid3->fp, $fieldarray['offset'], SEEK_SET);
|
||||||
|
$info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($this->getid3->fp, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]);
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch ($fieldarray['raw']['tag']) {
|
||||||
|
case 256: // ImageWidth
|
||||||
|
$info['video']['resolution_x'] = $fieldarray['value'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 257: // ImageLength
|
||||||
|
$info['video']['resolution_y'] = $fieldarray['value'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 258: // BitsPerSample
|
||||||
|
if (isset($fieldarray['value'])) {
|
||||||
|
$info['video']['bits_per_sample'] = $fieldarray['value'];
|
||||||
|
} else {
|
||||||
|
$info['video']['bits_per_sample'] = 0;
|
||||||
|
for ($i = 0; $i < $fieldarray['raw']['length']; $i++) {
|
||||||
|
$info['video']['bits_per_sample'] += $this->TIFFendian2Int(substr($info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'], $i * $FieldTypeByteLength[$fieldarray['raw']['type']], $FieldTypeByteLength[$fieldarray['raw']['type']]), $info['tiff']['byte_order']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 259: // Compression
|
||||||
|
$info['video']['codec'] = $this->TIFFcompressionMethod($fieldarray['value']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 270: // ImageDescription
|
||||||
|
case 271: // Make
|
||||||
|
case 272: // Model
|
||||||
|
case 305: // Software
|
||||||
|
case 306: // DateTime
|
||||||
|
case 315: // Artist
|
||||||
|
case 316: // HostComputer
|
||||||
|
$TIFFcommentName = $this->TIFFcommentName($fieldarray['raw']['tag']);
|
||||||
|
if (isset($info['tiff']['comments'][$TIFFcommentName])) {
|
||||||
|
$info['tiff']['comments'][$TIFFcommentName][] = $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'];
|
||||||
|
} else {
|
||||||
|
$info['tiff']['comments'][$TIFFcommentName] = array($info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data']);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function TIFFendian2Int($bytestring, $byteorder) {
|
||||||
|
if ($byteorder == 'Intel') {
|
||||||
|
return getid3_lib::LittleEndian2Int($bytestring);
|
||||||
|
} elseif ($byteorder == 'Motorola') {
|
||||||
|
return getid3_lib::BigEndian2Int($bytestring);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TIFFcompressionMethod($id) {
|
||||||
|
static $TIFFcompressionMethod = array();
|
||||||
|
if (empty($TIFFcompressionMethod)) {
|
||||||
|
$TIFFcompressionMethod = array(
|
||||||
|
1 => 'Uncompressed',
|
||||||
|
2 => 'Huffman',
|
||||||
|
3 => 'Fax - CCITT 3',
|
||||||
|
5 => 'LZW',
|
||||||
|
32773 => 'PackBits',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (isset($TIFFcompressionMethod[$id]) ? $TIFFcompressionMethod[$id] : 'unknown/invalid ('.$id.')');
|
||||||
|
}
|
||||||
|
|
||||||
|
function TIFFcommentName($id) {
|
||||||
|
static $TIFFcommentName = array();
|
||||||
|
if (empty($TIFFcommentName)) {
|
||||||
|
$TIFFcommentName = array(
|
||||||
|
270 => 'imagedescription',
|
||||||
|
271 => 'make',
|
||||||
|
272 => 'model',
|
||||||
|
305 => 'software',
|
||||||
|
306 => 'datetime',
|
||||||
|
315 => 'artist',
|
||||||
|
316 => 'hostcomputer',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (isset($TIFFcommentName[$id]) ? $TIFFcommentName[$id] : 'unknown/invalid ('.$id.')');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
312
app/library/getid3/module.misc.cue.php
Normal file
312
app/library/getid3/module.misc.cue.php
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.misc.cue.php //
|
||||||
|
// module for analyzing CUEsheet files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// Module originally written [2009-Mar-25] by //
|
||||||
|
// Nigel Barnes <ngbarnesØhotmail*com> //
|
||||||
|
// Minor reformatting and similar small changes to integrate //
|
||||||
|
// into getID3 by James Heinrich <info@getid3.org> //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CueSheet parser by Nigel Barnes.
|
||||||
|
*
|
||||||
|
* This is a PHP conversion of CueSharp 0.5 by Wyatt O'Day (wyday.com/cuesharp)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A CueSheet class used to open and parse cuesheets.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class getid3_cue extends getid3_handler
|
||||||
|
{
|
||||||
|
var $cuesheet = array();
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'cue';
|
||||||
|
$this->readCueSheetFilename($info['filenamepath']);
|
||||||
|
$info['cue'] = $this->cuesheet;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function readCueSheetFilename($filename)
|
||||||
|
{
|
||||||
|
$filedata = file_get_contents($filename);
|
||||||
|
return $this->readCueSheet($filedata);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Parses a cue sheet file.
|
||||||
|
*
|
||||||
|
* @param string $filename - The filename for the cue sheet to open.
|
||||||
|
*/
|
||||||
|
function readCueSheet(&$filedata)
|
||||||
|
{
|
||||||
|
$cue_lines = array();
|
||||||
|
foreach (explode("\n", str_replace("\r", null, $filedata)) as $line)
|
||||||
|
{
|
||||||
|
if ( (strlen($line) > 0) && ($line[0] != '#'))
|
||||||
|
{
|
||||||
|
$cue_lines[] = trim($line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->parseCueSheet($cue_lines);
|
||||||
|
|
||||||
|
return $this->cuesheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the cue sheet array.
|
||||||
|
*
|
||||||
|
* @param array $file - The cuesheet as an array of each line.
|
||||||
|
*/
|
||||||
|
function parseCueSheet($file)
|
||||||
|
{
|
||||||
|
//-1 means still global, all others are track specific
|
||||||
|
$track_on = -1;
|
||||||
|
|
||||||
|
for ($i=0; $i < count($file); $i++)
|
||||||
|
{
|
||||||
|
list($key) = explode(' ', strtolower($file[$i]), 2);
|
||||||
|
switch ($key)
|
||||||
|
{
|
||||||
|
case 'catalog':
|
||||||
|
case 'cdtextfile':
|
||||||
|
case 'isrc':
|
||||||
|
case 'performer':
|
||||||
|
case 'songwriter':
|
||||||
|
case 'title':
|
||||||
|
$this->parseString($file[$i], $track_on);
|
||||||
|
break;
|
||||||
|
case 'file':
|
||||||
|
$currentFile = $this->parseFile($file[$i]);
|
||||||
|
break;
|
||||||
|
case 'flags':
|
||||||
|
$this->parseFlags($file[$i], $track_on);
|
||||||
|
break;
|
||||||
|
case 'index':
|
||||||
|
case 'postgap':
|
||||||
|
case 'pregap':
|
||||||
|
$this->parseIndex($file[$i], $track_on);
|
||||||
|
break;
|
||||||
|
case 'rem':
|
||||||
|
$this->parseComment($file[$i], $track_on);
|
||||||
|
break;
|
||||||
|
case 'track':
|
||||||
|
$track_on++;
|
||||||
|
$this->parseTrack($file[$i], $track_on);
|
||||||
|
if (isset($currentFile)) // if there's a file
|
||||||
|
{
|
||||||
|
$this->cuesheet['tracks'][$track_on]['datafile'] = $currentFile;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
//save discarded junk and place string[] with track it was found in
|
||||||
|
$this->parseGarbage($file[$i], $track_on);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the REM command.
|
||||||
|
*
|
||||||
|
* @param string $line - The line in the cue file that contains the TRACK command.
|
||||||
|
* @param integer $track_on - The track currently processing.
|
||||||
|
*/
|
||||||
|
function parseComment($line, $track_on)
|
||||||
|
{
|
||||||
|
$explodedline = explode(' ', $line, 3);
|
||||||
|
$comment_REM = (isset($explodedline[0]) ? $explodedline[0] : '');
|
||||||
|
$comment_type = (isset($explodedline[1]) ? $explodedline[1] : '');
|
||||||
|
$comment_data = (isset($explodedline[2]) ? $explodedline[2] : '');
|
||||||
|
if (($comment_REM == 'REM') && $comment_type) {
|
||||||
|
$comment_type = strtolower($comment_type);
|
||||||
|
$commment_data = trim($comment_data, ' "');
|
||||||
|
if ($track_on != -1) {
|
||||||
|
$this->cuesheet['tracks'][$track_on]['comments'][$comment_type][] = $comment_data;
|
||||||
|
} else {
|
||||||
|
$this->cuesheet['comments'][$comment_type][] = $comment_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the FILE command.
|
||||||
|
*
|
||||||
|
* @param string $line - The line in the cue file that contains the FILE command.
|
||||||
|
* @return array - Array of FILENAME and TYPE of file..
|
||||||
|
*/
|
||||||
|
function parseFile($line)
|
||||||
|
{
|
||||||
|
$line = substr($line, strpos($line, ' ') + 1);
|
||||||
|
$type = strtolower(substr($line, strrpos($line, ' ')));
|
||||||
|
|
||||||
|
//remove type
|
||||||
|
$line = substr($line, 0, strrpos($line, ' ') - 1);
|
||||||
|
|
||||||
|
//if quotes around it, remove them.
|
||||||
|
$line = trim($line, '"');
|
||||||
|
|
||||||
|
return array('filename'=>$line, 'type'=>$type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the FLAG command.
|
||||||
|
*
|
||||||
|
* @param string $line - The line in the cue file that contains the TRACK command.
|
||||||
|
* @param integer $track_on - The track currently processing.
|
||||||
|
*/
|
||||||
|
function parseFlags($line, $track_on)
|
||||||
|
{
|
||||||
|
if ($track_on != -1)
|
||||||
|
{
|
||||||
|
foreach (explode(' ', strtolower($line)) as $type)
|
||||||
|
{
|
||||||
|
switch ($type)
|
||||||
|
{
|
||||||
|
case 'flags':
|
||||||
|
// first entry in this line
|
||||||
|
$this->cuesheet['tracks'][$track_on]['flags'] = array(
|
||||||
|
'4ch' => false,
|
||||||
|
'data' => false,
|
||||||
|
'dcp' => false,
|
||||||
|
'pre' => false,
|
||||||
|
'scms' => false,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'data':
|
||||||
|
case 'dcp':
|
||||||
|
case '4ch':
|
||||||
|
case 'pre':
|
||||||
|
case 'scms':
|
||||||
|
$this->cuesheet['tracks'][$track_on]['flags'][$type] = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect any unidentified data.
|
||||||
|
*
|
||||||
|
* @param string $line - The line in the cue file that contains the TRACK command.
|
||||||
|
* @param integer $track_on - The track currently processing.
|
||||||
|
*/
|
||||||
|
function parseGarbage($line, $track_on)
|
||||||
|
{
|
||||||
|
if ( strlen($line) > 0 )
|
||||||
|
{
|
||||||
|
if ($track_on == -1)
|
||||||
|
{
|
||||||
|
$this->cuesheet['garbage'][] = $line;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->cuesheet['tracks'][$track_on]['garbage'][] = $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the INDEX command of a TRACK.
|
||||||
|
*
|
||||||
|
* @param string $line - The line in the cue file that contains the TRACK command.
|
||||||
|
* @param integer $track_on - The track currently processing.
|
||||||
|
*/
|
||||||
|
function parseIndex($line, $track_on)
|
||||||
|
{
|
||||||
|
$type = strtolower(substr($line, 0, strpos($line, ' ')));
|
||||||
|
$line = substr($line, strpos($line, ' ') + 1);
|
||||||
|
|
||||||
|
if ($type == 'index')
|
||||||
|
{
|
||||||
|
//read the index number
|
||||||
|
$number = intval(substr($line, 0, strpos($line, ' ')));
|
||||||
|
$line = substr($line, strpos($line, ' ') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//extract the minutes, seconds, and frames
|
||||||
|
$explodedline = explode(':', $line);
|
||||||
|
$minutes = (isset($explodedline[0]) ? $explodedline[0] : '');
|
||||||
|
$seconds = (isset($explodedline[1]) ? $explodedline[1] : '');
|
||||||
|
$frames = (isset($explodedline[2]) ? $explodedline[2] : '');
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case 'index':
|
||||||
|
$this->cuesheet['tracks'][$track_on][$type][$number] = array('minutes'=>intval($minutes), 'seconds'=>intval($seconds), 'frames'=>intval($frames));
|
||||||
|
break;
|
||||||
|
case 'pregap':
|
||||||
|
case 'postgap':
|
||||||
|
$this->cuesheet['tracks'][$track_on][$type] = array('minutes'=>intval($minutes), 'seconds'=>intval($seconds), 'frames'=>intval($frames));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseString($line, $track_on)
|
||||||
|
{
|
||||||
|
$category = strtolower(substr($line, 0, strpos($line, ' ')));
|
||||||
|
$line = substr($line, strpos($line, ' ') + 1);
|
||||||
|
|
||||||
|
//get rid of the quotes
|
||||||
|
$line = trim($line, '"');
|
||||||
|
|
||||||
|
switch ($category)
|
||||||
|
{
|
||||||
|
case 'catalog':
|
||||||
|
case 'cdtextfile':
|
||||||
|
case 'isrc':
|
||||||
|
case 'performer':
|
||||||
|
case 'songwriter':
|
||||||
|
case 'title':
|
||||||
|
if ($track_on == -1)
|
||||||
|
{
|
||||||
|
$this->cuesheet[$category] = $line;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->cuesheet['tracks'][$track_on][$category] = $line;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the TRACK command.
|
||||||
|
*
|
||||||
|
* @param string $line - The line in the cue file that contains the TRACK command.
|
||||||
|
* @param integer $track_on - The track currently processing.
|
||||||
|
*/
|
||||||
|
function parseTrack($line, $track_on)
|
||||||
|
{
|
||||||
|
$line = substr($line, strpos($line, ' ') + 1);
|
||||||
|
$track = ltrim(substr($line, 0, strpos($line, ' ')), '0');
|
||||||
|
|
||||||
|
//find the data type.
|
||||||
|
$datatype = strtolower(substr($line, strpos($line, ' ') + 1));
|
||||||
|
|
||||||
|
$this->cuesheet['tracks'][$track_on] = array('track_number'=>$track, 'datatype'=>$datatype);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
61
app/library/getid3/module.misc.exe.php
Normal file
61
app/library/getid3/module.misc.exe.php
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.misc.exe.php //
|
||||||
|
// module for analyzing EXE files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_exe extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$EXEheader = fread($this->getid3->fp, 28);
|
||||||
|
|
||||||
|
$magic = 'MZ';
|
||||||
|
if (substr($EXEheader, 0, 2) != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($EXEheader, 0, 2)).'"';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['fileformat'] = 'exe';
|
||||||
|
$info['exe']['mz']['magic'] = 'MZ';
|
||||||
|
|
||||||
|
$info['exe']['mz']['raw']['last_page_size'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 2, 2));
|
||||||
|
$info['exe']['mz']['raw']['page_count'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 4, 2));
|
||||||
|
$info['exe']['mz']['raw']['relocation_count'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 6, 2));
|
||||||
|
$info['exe']['mz']['raw']['header_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 8, 2));
|
||||||
|
$info['exe']['mz']['raw']['min_memory_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 10, 2));
|
||||||
|
$info['exe']['mz']['raw']['max_memory_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 12, 2));
|
||||||
|
$info['exe']['mz']['raw']['initial_ss'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 14, 2));
|
||||||
|
$info['exe']['mz']['raw']['initial_sp'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 16, 2));
|
||||||
|
$info['exe']['mz']['raw']['checksum'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 18, 2));
|
||||||
|
$info['exe']['mz']['raw']['cs_ip'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 20, 4));
|
||||||
|
$info['exe']['mz']['raw']['relocation_table_offset'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 24, 2));
|
||||||
|
$info['exe']['mz']['raw']['overlay_number'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 26, 2));
|
||||||
|
|
||||||
|
$info['exe']['mz']['byte_size'] = (($info['exe']['mz']['raw']['page_count'] - 1)) * 512 + $info['exe']['mz']['raw']['last_page_size'];
|
||||||
|
$info['exe']['mz']['header_size'] = $info['exe']['mz']['raw']['header_paragraphs'] * 16;
|
||||||
|
$info['exe']['mz']['memory_minimum'] = $info['exe']['mz']['raw']['min_memory_paragraphs'] * 16;
|
||||||
|
$info['exe']['mz']['memory_recommended'] = $info['exe']['mz']['raw']['max_memory_paragraphs'] * 16;
|
||||||
|
|
||||||
|
$info['error'][] = 'EXE parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
389
app/library/getid3/module.misc.iso.php
Normal file
389
app/library/getid3/module.misc.iso.php
Normal file
|
@ -0,0 +1,389 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.misc.iso.php //
|
||||||
|
// module for analyzing ISO files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_iso extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'iso';
|
||||||
|
|
||||||
|
for ($i = 16; $i <= 19; $i++) {
|
||||||
|
fseek($this->getid3->fp, 2048 * $i, SEEK_SET);
|
||||||
|
$ISOheader = fread($this->getid3->fp, 2048);
|
||||||
|
if (substr($ISOheader, 1, 5) == 'CD001') {
|
||||||
|
switch (ord($ISOheader{0})) {
|
||||||
|
case 1:
|
||||||
|
$info['iso']['primary_volume_descriptor']['offset'] = 2048 * $i;
|
||||||
|
$this->ParsePrimaryVolumeDescriptor($ISOheader);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
$info['iso']['supplementary_volume_descriptor']['offset'] = 2048 * $i;
|
||||||
|
$this->ParseSupplementaryVolumeDescriptor($ISOheader);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// skip
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->ParsePathTable();
|
||||||
|
|
||||||
|
$info['iso']['files'] = array();
|
||||||
|
foreach ($info['iso']['path_table']['directories'] as $directorynum => $directorydata) {
|
||||||
|
$info['iso']['directories'][$directorynum] = $this->ParseDirectoryRecord($directorydata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ParsePrimaryVolumeDescriptor(&$ISOheader) {
|
||||||
|
// ISO integer values are stored *BOTH* Little-Endian AND Big-Endian format!!
|
||||||
|
// ie 12345 == 0x3039 is stored as $39 $30 $30 $39 in a 4-byte field
|
||||||
|
|
||||||
|
// shortcuts
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
$info['iso']['primary_volume_descriptor']['raw'] = array();
|
||||||
|
$thisfile_iso_primaryVD = &$info['iso']['primary_volume_descriptor'];
|
||||||
|
$thisfile_iso_primaryVD_raw = &$thisfile_iso_primaryVD['raw'];
|
||||||
|
|
||||||
|
$thisfile_iso_primaryVD_raw['volume_descriptor_type'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 0, 1));
|
||||||
|
$thisfile_iso_primaryVD_raw['standard_identifier'] = substr($ISOheader, 1, 5);
|
||||||
|
if ($thisfile_iso_primaryVD_raw['standard_identifier'] != 'CD001') {
|
||||||
|
$info['error'][] = 'Expected "CD001" at offset ('.($thisfile_iso_primaryVD['offset'] + 1).'), found "'.$thisfile_iso_primaryVD_raw['standard_identifier'].'" instead';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['iso']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$thisfile_iso_primaryVD_raw['volume_descriptor_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 6, 1));
|
||||||
|
//$thisfile_iso_primaryVD_raw['unused_1'] = substr($ISOheader, 7, 1);
|
||||||
|
$thisfile_iso_primaryVD_raw['system_identifier'] = substr($ISOheader, 8, 32);
|
||||||
|
$thisfile_iso_primaryVD_raw['volume_identifier'] = substr($ISOheader, 40, 32);
|
||||||
|
//$thisfile_iso_primaryVD_raw['unused_2'] = substr($ISOheader, 72, 8);
|
||||||
|
$thisfile_iso_primaryVD_raw['volume_space_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 80, 4));
|
||||||
|
//$thisfile_iso_primaryVD_raw['unused_3'] = substr($ISOheader, 88, 32);
|
||||||
|
$thisfile_iso_primaryVD_raw['volume_set_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 120, 2));
|
||||||
|
$thisfile_iso_primaryVD_raw['volume_sequence_number'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 124, 2));
|
||||||
|
$thisfile_iso_primaryVD_raw['logical_block_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 128, 2));
|
||||||
|
$thisfile_iso_primaryVD_raw['path_table_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 132, 4));
|
||||||
|
$thisfile_iso_primaryVD_raw['path_table_l_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 140, 2));
|
||||||
|
$thisfile_iso_primaryVD_raw['path_table_l_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 144, 2));
|
||||||
|
$thisfile_iso_primaryVD_raw['path_table_m_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 148, 2));
|
||||||
|
$thisfile_iso_primaryVD_raw['path_table_m_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 152, 2));
|
||||||
|
$thisfile_iso_primaryVD_raw['root_directory_record'] = substr($ISOheader, 156, 34);
|
||||||
|
$thisfile_iso_primaryVD_raw['volume_set_identifier'] = substr($ISOheader, 190, 128);
|
||||||
|
$thisfile_iso_primaryVD_raw['publisher_identifier'] = substr($ISOheader, 318, 128);
|
||||||
|
$thisfile_iso_primaryVD_raw['data_preparer_identifier'] = substr($ISOheader, 446, 128);
|
||||||
|
$thisfile_iso_primaryVD_raw['application_identifier'] = substr($ISOheader, 574, 128);
|
||||||
|
$thisfile_iso_primaryVD_raw['copyright_file_identifier'] = substr($ISOheader, 702, 37);
|
||||||
|
$thisfile_iso_primaryVD_raw['abstract_file_identifier'] = substr($ISOheader, 739, 37);
|
||||||
|
$thisfile_iso_primaryVD_raw['bibliographic_file_identifier'] = substr($ISOheader, 776, 37);
|
||||||
|
$thisfile_iso_primaryVD_raw['volume_creation_date_time'] = substr($ISOheader, 813, 17);
|
||||||
|
$thisfile_iso_primaryVD_raw['volume_modification_date_time'] = substr($ISOheader, 830, 17);
|
||||||
|
$thisfile_iso_primaryVD_raw['volume_expiration_date_time'] = substr($ISOheader, 847, 17);
|
||||||
|
$thisfile_iso_primaryVD_raw['volume_effective_date_time'] = substr($ISOheader, 864, 17);
|
||||||
|
$thisfile_iso_primaryVD_raw['file_structure_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 881, 1));
|
||||||
|
//$thisfile_iso_primaryVD_raw['unused_4'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 882, 1));
|
||||||
|
$thisfile_iso_primaryVD_raw['application_data'] = substr($ISOheader, 883, 512);
|
||||||
|
//$thisfile_iso_primaryVD_raw['unused_5'] = substr($ISOheader, 1395, 653);
|
||||||
|
|
||||||
|
$thisfile_iso_primaryVD['system_identifier'] = trim($thisfile_iso_primaryVD_raw['system_identifier']);
|
||||||
|
$thisfile_iso_primaryVD['volume_identifier'] = trim($thisfile_iso_primaryVD_raw['volume_identifier']);
|
||||||
|
$thisfile_iso_primaryVD['volume_set_identifier'] = trim($thisfile_iso_primaryVD_raw['volume_set_identifier']);
|
||||||
|
$thisfile_iso_primaryVD['publisher_identifier'] = trim($thisfile_iso_primaryVD_raw['publisher_identifier']);
|
||||||
|
$thisfile_iso_primaryVD['data_preparer_identifier'] = trim($thisfile_iso_primaryVD_raw['data_preparer_identifier']);
|
||||||
|
$thisfile_iso_primaryVD['application_identifier'] = trim($thisfile_iso_primaryVD_raw['application_identifier']);
|
||||||
|
$thisfile_iso_primaryVD['copyright_file_identifier'] = trim($thisfile_iso_primaryVD_raw['copyright_file_identifier']);
|
||||||
|
$thisfile_iso_primaryVD['abstract_file_identifier'] = trim($thisfile_iso_primaryVD_raw['abstract_file_identifier']);
|
||||||
|
$thisfile_iso_primaryVD['bibliographic_file_identifier'] = trim($thisfile_iso_primaryVD_raw['bibliographic_file_identifier']);
|
||||||
|
$thisfile_iso_primaryVD['volume_creation_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_creation_date_time']);
|
||||||
|
$thisfile_iso_primaryVD['volume_modification_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_modification_date_time']);
|
||||||
|
$thisfile_iso_primaryVD['volume_expiration_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_expiration_date_time']);
|
||||||
|
$thisfile_iso_primaryVD['volume_effective_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_effective_date_time']);
|
||||||
|
|
||||||
|
if (($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048) > $info['filesize']) {
|
||||||
|
$info['error'][] = 'Volume Space Size ('.($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048).' bytes) is larger than the file size ('.$info['filesize'].' bytes) (truncated file?)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ParseSupplementaryVolumeDescriptor(&$ISOheader) {
|
||||||
|
// ISO integer values are stored Both-Endian format!!
|
||||||
|
// ie 12345 == 0x3039 is stored as $39 $30 $30 $39 in a 4-byte field
|
||||||
|
|
||||||
|
// shortcuts
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
$info['iso']['supplementary_volume_descriptor']['raw'] = array();
|
||||||
|
$thisfile_iso_supplementaryVD = &$info['iso']['supplementary_volume_descriptor'];
|
||||||
|
$thisfile_iso_supplementaryVD_raw = &$thisfile_iso_supplementaryVD['raw'];
|
||||||
|
|
||||||
|
$thisfile_iso_supplementaryVD_raw['volume_descriptor_type'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 0, 1));
|
||||||
|
$thisfile_iso_supplementaryVD_raw['standard_identifier'] = substr($ISOheader, 1, 5);
|
||||||
|
if ($thisfile_iso_supplementaryVD_raw['standard_identifier'] != 'CD001') {
|
||||||
|
$info['error'][] = 'Expected "CD001" at offset ('.($thisfile_iso_supplementaryVD['offset'] + 1).'), found "'.$thisfile_iso_supplementaryVD_raw['standard_identifier'].'" instead';
|
||||||
|
unset($info['fileformat']);
|
||||||
|
unset($info['iso']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisfile_iso_supplementaryVD_raw['volume_descriptor_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 6, 1));
|
||||||
|
//$thisfile_iso_supplementaryVD_raw['unused_1'] = substr($ISOheader, 7, 1);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['system_identifier'] = substr($ISOheader, 8, 32);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['volume_identifier'] = substr($ISOheader, 40, 32);
|
||||||
|
//$thisfile_iso_supplementaryVD_raw['unused_2'] = substr($ISOheader, 72, 8);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['volume_space_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 80, 4));
|
||||||
|
if ($thisfile_iso_supplementaryVD_raw['volume_space_size'] == 0) {
|
||||||
|
// Supplementary Volume Descriptor not used
|
||||||
|
//unset($thisfile_iso_supplementaryVD);
|
||||||
|
//return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//$thisfile_iso_supplementaryVD_raw['unused_3'] = substr($ISOheader, 88, 32);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['volume_set_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 120, 2));
|
||||||
|
$thisfile_iso_supplementaryVD_raw['volume_sequence_number'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 124, 2));
|
||||||
|
$thisfile_iso_supplementaryVD_raw['logical_block_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 128, 2));
|
||||||
|
$thisfile_iso_supplementaryVD_raw['path_table_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 132, 4));
|
||||||
|
$thisfile_iso_supplementaryVD_raw['path_table_l_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 140, 2));
|
||||||
|
$thisfile_iso_supplementaryVD_raw['path_table_l_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 144, 2));
|
||||||
|
$thisfile_iso_supplementaryVD_raw['path_table_m_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 148, 2));
|
||||||
|
$thisfile_iso_supplementaryVD_raw['path_table_m_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 152, 2));
|
||||||
|
$thisfile_iso_supplementaryVD_raw['root_directory_record'] = substr($ISOheader, 156, 34);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['volume_set_identifier'] = substr($ISOheader, 190, 128);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['publisher_identifier'] = substr($ISOheader, 318, 128);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['data_preparer_identifier'] = substr($ISOheader, 446, 128);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['application_identifier'] = substr($ISOheader, 574, 128);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['copyright_file_identifier'] = substr($ISOheader, 702, 37);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['abstract_file_identifier'] = substr($ISOheader, 739, 37);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['bibliographic_file_identifier'] = substr($ISOheader, 776, 37);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['volume_creation_date_time'] = substr($ISOheader, 813, 17);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['volume_modification_date_time'] = substr($ISOheader, 830, 17);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['volume_expiration_date_time'] = substr($ISOheader, 847, 17);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['volume_effective_date_time'] = substr($ISOheader, 864, 17);
|
||||||
|
$thisfile_iso_supplementaryVD_raw['file_structure_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 881, 1));
|
||||||
|
//$thisfile_iso_supplementaryVD_raw['unused_4'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 882, 1));
|
||||||
|
$thisfile_iso_supplementaryVD_raw['application_data'] = substr($ISOheader, 883, 512);
|
||||||
|
//$thisfile_iso_supplementaryVD_raw['unused_5'] = substr($ISOheader, 1395, 653);
|
||||||
|
|
||||||
|
$thisfile_iso_supplementaryVD['system_identifier'] = trim($thisfile_iso_supplementaryVD_raw['system_identifier']);
|
||||||
|
$thisfile_iso_supplementaryVD['volume_identifier'] = trim($thisfile_iso_supplementaryVD_raw['volume_identifier']);
|
||||||
|
$thisfile_iso_supplementaryVD['volume_set_identifier'] = trim($thisfile_iso_supplementaryVD_raw['volume_set_identifier']);
|
||||||
|
$thisfile_iso_supplementaryVD['publisher_identifier'] = trim($thisfile_iso_supplementaryVD_raw['publisher_identifier']);
|
||||||
|
$thisfile_iso_supplementaryVD['data_preparer_identifier'] = trim($thisfile_iso_supplementaryVD_raw['data_preparer_identifier']);
|
||||||
|
$thisfile_iso_supplementaryVD['application_identifier'] = trim($thisfile_iso_supplementaryVD_raw['application_identifier']);
|
||||||
|
$thisfile_iso_supplementaryVD['copyright_file_identifier'] = trim($thisfile_iso_supplementaryVD_raw['copyright_file_identifier']);
|
||||||
|
$thisfile_iso_supplementaryVD['abstract_file_identifier'] = trim($thisfile_iso_supplementaryVD_raw['abstract_file_identifier']);
|
||||||
|
$thisfile_iso_supplementaryVD['bibliographic_file_identifier'] = trim($thisfile_iso_supplementaryVD_raw['bibliographic_file_identifier']);
|
||||||
|
$thisfile_iso_supplementaryVD['volume_creation_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_creation_date_time']);
|
||||||
|
$thisfile_iso_supplementaryVD['volume_modification_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_modification_date_time']);
|
||||||
|
$thisfile_iso_supplementaryVD['volume_expiration_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_expiration_date_time']);
|
||||||
|
$thisfile_iso_supplementaryVD['volume_effective_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_effective_date_time']);
|
||||||
|
|
||||||
|
if (($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']) > $info['filesize']) {
|
||||||
|
$info['error'][] = 'Volume Space Size ('.($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']).' bytes) is larger than the file size ('.$info['filesize'].' bytes) (truncated file?)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ParsePathTable() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
if (!isset($info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location']) && !isset($info['iso']['primary_volume_descriptor']['raw']['path_table_l_location'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isset($info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location'])) {
|
||||||
|
$PathTableLocation = $info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location'];
|
||||||
|
$PathTableSize = $info['iso']['supplementary_volume_descriptor']['raw']['path_table_size'];
|
||||||
|
$TextEncoding = 'UTF-16BE'; // Big-Endian Unicode
|
||||||
|
} else {
|
||||||
|
$PathTableLocation = $info['iso']['primary_volume_descriptor']['raw']['path_table_l_location'];
|
||||||
|
$PathTableSize = $info['iso']['primary_volume_descriptor']['raw']['path_table_size'];
|
||||||
|
$TextEncoding = 'ISO-8859-1'; // Latin-1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($PathTableLocation * 2048) > $info['filesize']) {
|
||||||
|
$info['error'][] = 'Path Table Location specifies an offset ('.($PathTableLocation * 2048).') beyond the end-of-file ('.$info['filesize'].')';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['iso']['path_table']['offset'] = $PathTableLocation * 2048;
|
||||||
|
fseek($this->getid3->fp, $info['iso']['path_table']['offset'], SEEK_SET);
|
||||||
|
$info['iso']['path_table']['raw'] = fread($this->getid3->fp, $PathTableSize);
|
||||||
|
|
||||||
|
$offset = 0;
|
||||||
|
$pathcounter = 1;
|
||||||
|
while ($offset < $PathTableSize) {
|
||||||
|
// shortcut
|
||||||
|
$info['iso']['path_table']['directories'][$pathcounter] = array();
|
||||||
|
$thisfile_iso_pathtable_directories_current = &$info['iso']['path_table']['directories'][$pathcounter];
|
||||||
|
|
||||||
|
$thisfile_iso_pathtable_directories_current['length'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
$thisfile_iso_pathtable_directories_current['extended_length'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 1));
|
||||||
|
$offset += 1;
|
||||||
|
$thisfile_iso_pathtable_directories_current['location_logical'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$thisfile_iso_pathtable_directories_current['parent_directory'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 2));
|
||||||
|
$offset += 2;
|
||||||
|
$thisfile_iso_pathtable_directories_current['name'] = substr($info['iso']['path_table']['raw'], $offset, $thisfile_iso_pathtable_directories_current['length']);
|
||||||
|
$offset += $thisfile_iso_pathtable_directories_current['length'] + ($thisfile_iso_pathtable_directories_current['length'] % 2);
|
||||||
|
|
||||||
|
$thisfile_iso_pathtable_directories_current['name_ascii'] = getid3_lib::iconv_fallback($TextEncoding, $info['encoding'], $thisfile_iso_pathtable_directories_current['name']);
|
||||||
|
|
||||||
|
$thisfile_iso_pathtable_directories_current['location_bytes'] = $thisfile_iso_pathtable_directories_current['location_logical'] * 2048;
|
||||||
|
if ($pathcounter == 1) {
|
||||||
|
$thisfile_iso_pathtable_directories_current['full_path'] = '/';
|
||||||
|
} else {
|
||||||
|
$thisfile_iso_pathtable_directories_current['full_path'] = $info['iso']['path_table']['directories'][$thisfile_iso_pathtable_directories_current['parent_directory']]['full_path'].$thisfile_iso_pathtable_directories_current['name_ascii'].'/';
|
||||||
|
}
|
||||||
|
$FullPathArray[] = $thisfile_iso_pathtable_directories_current['full_path'];
|
||||||
|
|
||||||
|
$pathcounter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ParseDirectoryRecord($directorydata) {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
if (isset($info['iso']['supplementary_volume_descriptor'])) {
|
||||||
|
$TextEncoding = 'UTF-16BE'; // Big-Endian Unicode
|
||||||
|
} else {
|
||||||
|
$TextEncoding = 'ISO-8859-1'; // Latin-1
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $directorydata['location_bytes'], SEEK_SET);
|
||||||
|
$DirectoryRecordData = fread($this->getid3->fp, 1);
|
||||||
|
|
||||||
|
while (ord($DirectoryRecordData{0}) > 33) {
|
||||||
|
|
||||||
|
$DirectoryRecordData .= fread($this->getid3->fp, ord($DirectoryRecordData{0}) - 1);
|
||||||
|
|
||||||
|
$ThisDirectoryRecord['raw']['length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 0, 1));
|
||||||
|
$ThisDirectoryRecord['raw']['extended_attribute_length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 1, 1));
|
||||||
|
$ThisDirectoryRecord['raw']['offset_logical'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 2, 4));
|
||||||
|
$ThisDirectoryRecord['raw']['filesize'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 10, 4));
|
||||||
|
$ThisDirectoryRecord['raw']['recording_date_time'] = substr($DirectoryRecordData, 18, 7);
|
||||||
|
$ThisDirectoryRecord['raw']['file_flags'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 25, 1));
|
||||||
|
$ThisDirectoryRecord['raw']['file_unit_size'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 26, 1));
|
||||||
|
$ThisDirectoryRecord['raw']['interleave_gap_size'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 27, 1));
|
||||||
|
$ThisDirectoryRecord['raw']['volume_sequence_number'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 28, 2));
|
||||||
|
$ThisDirectoryRecord['raw']['file_identifier_length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 32, 1));
|
||||||
|
$ThisDirectoryRecord['raw']['file_identifier'] = substr($DirectoryRecordData, 33, $ThisDirectoryRecord['raw']['file_identifier_length']);
|
||||||
|
|
||||||
|
$ThisDirectoryRecord['file_identifier_ascii'] = getid3_lib::iconv_fallback($TextEncoding, $info['encoding'], $ThisDirectoryRecord['raw']['file_identifier']);
|
||||||
|
|
||||||
|
$ThisDirectoryRecord['filesize'] = $ThisDirectoryRecord['raw']['filesize'];
|
||||||
|
$ThisDirectoryRecord['offset_bytes'] = $ThisDirectoryRecord['raw']['offset_logical'] * 2048;
|
||||||
|
$ThisDirectoryRecord['file_flags']['hidden'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x01);
|
||||||
|
$ThisDirectoryRecord['file_flags']['directory'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x02);
|
||||||
|
$ThisDirectoryRecord['file_flags']['associated'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x04);
|
||||||
|
$ThisDirectoryRecord['file_flags']['extended'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x08);
|
||||||
|
$ThisDirectoryRecord['file_flags']['permissions'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x10);
|
||||||
|
$ThisDirectoryRecord['file_flags']['multiple'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x80);
|
||||||
|
$ThisDirectoryRecord['recording_timestamp'] = $this->ISOtime2UNIXtime($ThisDirectoryRecord['raw']['recording_date_time']);
|
||||||
|
|
||||||
|
if ($ThisDirectoryRecord['file_flags']['directory']) {
|
||||||
|
$ThisDirectoryRecord['filename'] = $directorydata['full_path'];
|
||||||
|
} else {
|
||||||
|
$ThisDirectoryRecord['filename'] = $directorydata['full_path'].$this->ISOstripFilenameVersion($ThisDirectoryRecord['file_identifier_ascii']);
|
||||||
|
$info['iso']['files'] = getid3_lib::array_merge_clobber($info['iso']['files'], getid3_lib::CreateDeepArray($ThisDirectoryRecord['filename'], '/', $ThisDirectoryRecord['filesize']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$DirectoryRecord[] = $ThisDirectoryRecord;
|
||||||
|
$DirectoryRecordData = fread($this->getid3->fp, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $DirectoryRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ISOstripFilenameVersion($ISOfilename) {
|
||||||
|
// convert 'filename.ext;1' to 'filename.ext'
|
||||||
|
if (!strstr($ISOfilename, ';')) {
|
||||||
|
return $ISOfilename;
|
||||||
|
} else {
|
||||||
|
return substr($ISOfilename, 0, strpos($ISOfilename, ';'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ISOtimeText2UNIXtime($ISOtime) {
|
||||||
|
|
||||||
|
$UNIXyear = (int) substr($ISOtime, 0, 4);
|
||||||
|
$UNIXmonth = (int) substr($ISOtime, 4, 2);
|
||||||
|
$UNIXday = (int) substr($ISOtime, 6, 2);
|
||||||
|
$UNIXhour = (int) substr($ISOtime, 8, 2);
|
||||||
|
$UNIXminute = (int) substr($ISOtime, 10, 2);
|
||||||
|
$UNIXsecond = (int) substr($ISOtime, 12, 2);
|
||||||
|
|
||||||
|
if (!$UNIXyear) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return gmmktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ISOtime2UNIXtime($ISOtime) {
|
||||||
|
// Represented by seven bytes:
|
||||||
|
// 1: Number of years since 1900
|
||||||
|
// 2: Month of the year from 1 to 12
|
||||||
|
// 3: Day of the Month from 1 to 31
|
||||||
|
// 4: Hour of the day from 0 to 23
|
||||||
|
// 5: Minute of the hour from 0 to 59
|
||||||
|
// 6: second of the minute from 0 to 59
|
||||||
|
// 7: Offset from Greenwich Mean Time in number of 15 minute intervals from -48 (West) to +52 (East)
|
||||||
|
|
||||||
|
$UNIXyear = ord($ISOtime{0}) + 1900;
|
||||||
|
$UNIXmonth = ord($ISOtime{1});
|
||||||
|
$UNIXday = ord($ISOtime{2});
|
||||||
|
$UNIXhour = ord($ISOtime{3});
|
||||||
|
$UNIXminute = ord($ISOtime{4});
|
||||||
|
$UNIXsecond = ord($ISOtime{5});
|
||||||
|
$GMToffset = $this->TwosCompliment2Decimal(ord($ISOtime{5}));
|
||||||
|
|
||||||
|
return gmmktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TwosCompliment2Decimal($BinaryValue) {
|
||||||
|
// http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html
|
||||||
|
// First check if the number is negative or positive by looking at the sign bit.
|
||||||
|
// If it is positive, simply convert it to decimal.
|
||||||
|
// If it is negative, make it positive by inverting the bits and adding one.
|
||||||
|
// Then, convert the result to decimal.
|
||||||
|
// The negative of this number is the value of the original binary.
|
||||||
|
|
||||||
|
if ($BinaryValue & 0x80) {
|
||||||
|
|
||||||
|
// negative number
|
||||||
|
return (0 - ((~$BinaryValue & 0xFF) + 1));
|
||||||
|
} else {
|
||||||
|
// positive number
|
||||||
|
return $BinaryValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
40
app/library/getid3/module.misc.msoffice.php
Normal file
40
app/library/getid3/module.misc.msoffice.php
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.archive.doc.php //
|
||||||
|
// module for analyzing MS Office (.doc, .xls, etc) files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_msoffice extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
|
||||||
|
$DOCFILEheader = fread($this->getid3->fp, 8);
|
||||||
|
$magic = "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1";
|
||||||
|
if (substr($DOCFILEheader, 0, 8) != $magic) {
|
||||||
|
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at '.$info['avdataoffset'].', found '.getid3_lib::PrintHexBytes(substr($DOCFILEheader, 0, 8)).' instead.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$info['fileformat'] = 'msoffice';
|
||||||
|
|
||||||
|
$info['error'][] = 'MS Office (.doc, .xls, etc) parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
33
app/library/getid3/module.misc.par2.php
Normal file
33
app/library/getid3/module.misc.par2.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.misc.par2.php //
|
||||||
|
// module for analyzing PAR2 files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_par2 extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'par2';
|
||||||
|
|
||||||
|
$info['error'][] = 'PAR2 parsing not enabled in this version of getID3()';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
33
app/library/getid3/module.misc.pdf.php
Normal file
33
app/library/getid3/module.misc.pdf.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.misc.pdf.php //
|
||||||
|
// module for analyzing PDF files //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_pdf extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
$info['fileformat'] = 'pdf';
|
||||||
|
|
||||||
|
$info['error'][] = 'PDF parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
372
app/library/getid3/module.tag.apetag.php
Normal file
372
app/library/getid3/module.tag.apetag.php
Normal file
|
@ -0,0 +1,372 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.tag.apetag.php //
|
||||||
|
// module for analyzing APE tags //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
class getid3_apetag extends getid3_handler
|
||||||
|
{
|
||||||
|
var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory
|
||||||
|
var $overrideendoffset = 0;
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
if (!getid3_lib::intValueSupported($info['filesize'])) {
|
||||||
|
$info['warning'][] = 'Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id3v1tagsize = 128;
|
||||||
|
$apetagheadersize = 32;
|
||||||
|
$lyrics3tagsize = 10;
|
||||||
|
|
||||||
|
if ($this->overrideendoffset == 0) {
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
|
||||||
|
$APEfooterID3v1 = fread($this->getid3->fp, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
|
||||||
|
|
||||||
|
//if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
|
||||||
|
if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
|
||||||
|
|
||||||
|
// APE tag found before ID3v1
|
||||||
|
$info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;
|
||||||
|
|
||||||
|
//} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
|
||||||
|
} elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
|
||||||
|
|
||||||
|
// APE tag found, no ID3v1
|
||||||
|
$info['ape']['tag_offset_end'] = $info['filesize'];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $this->overrideendoffset - $apetagheadersize, SEEK_SET);
|
||||||
|
if (fread($this->getid3->fp, 8) == 'APETAGEX') {
|
||||||
|
$info['ape']['tag_offset_end'] = $this->overrideendoffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!isset($info['ape']['tag_offset_end'])) {
|
||||||
|
|
||||||
|
// APE tag not found
|
||||||
|
unset($info['ape']);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$thisfile_ape = &$info['ape'];
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET);
|
||||||
|
$APEfooterData = fread($this->getid3->fp, 32);
|
||||||
|
if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
|
||||||
|
$info['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end'];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
|
||||||
|
fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET);
|
||||||
|
$thisfile_ape['tag_offset_start'] = ftell($this->getid3->fp);
|
||||||
|
$APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
|
||||||
|
} else {
|
||||||
|
$thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
|
||||||
|
fseek($this->getid3->fp, $thisfile_ape['tag_offset_start'], SEEK_SET);
|
||||||
|
$APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize']);
|
||||||
|
}
|
||||||
|
$info['avdataend'] = $thisfile_ape['tag_offset_start'];
|
||||||
|
|
||||||
|
if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
|
||||||
|
$info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data';
|
||||||
|
unset($info['id3v1']);
|
||||||
|
foreach ($info['warning'] as $key => $value) {
|
||||||
|
if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
|
||||||
|
unset($info['warning'][$key]);
|
||||||
|
sort($info['warning']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$offset = 0;
|
||||||
|
if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
|
||||||
|
if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
|
||||||
|
$offset += $apetagheadersize;
|
||||||
|
} else {
|
||||||
|
$info['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start'];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$info['replay_gain'] = array();
|
||||||
|
$thisfile_replaygain = &$info['replay_gain'];
|
||||||
|
|
||||||
|
for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
|
||||||
|
$value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
$item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
|
||||||
|
$offset += 4;
|
||||||
|
if (strstr(substr($APEtagData, $offset), "\x00") === false) {
|
||||||
|
$info['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
|
||||||
|
$item_key = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$thisfile_ape['items'][$item_key] = array();
|
||||||
|
$thisfile_ape_items_current = &$thisfile_ape['items'][$item_key];
|
||||||
|
|
||||||
|
$thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset;
|
||||||
|
|
||||||
|
$offset += ($ItemKeyLength + 1); // skip 0x00 terminator
|
||||||
|
$thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
|
||||||
|
$offset += $value_size;
|
||||||
|
|
||||||
|
$thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
|
||||||
|
switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
|
||||||
|
case 0: // UTF-8
|
||||||
|
case 3: // Locator (URL, filename, etc), UTF-8 encoded
|
||||||
|
$thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data']));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // binary data
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (strtolower($item_key)) {
|
||||||
|
case 'replaygain_track_gain':
|
||||||
|
$thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
||||||
|
$thisfile_replaygain['track']['originator'] = 'unspecified';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'replaygain_track_peak':
|
||||||
|
$thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
||||||
|
$thisfile_replaygain['track']['originator'] = 'unspecified';
|
||||||
|
if ($thisfile_replaygain['track']['peak'] <= 0) {
|
||||||
|
$info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'replaygain_album_gain':
|
||||||
|
$thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
||||||
|
$thisfile_replaygain['album']['originator'] = 'unspecified';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'replaygain_album_peak':
|
||||||
|
$thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
||||||
|
$thisfile_replaygain['album']['originator'] = 'unspecified';
|
||||||
|
if ($thisfile_replaygain['album']['peak'] <= 0) {
|
||||||
|
$info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'mp3gain_undo':
|
||||||
|
list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
|
||||||
|
$thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left);
|
||||||
|
$thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
|
||||||
|
$thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'mp3gain_minmax':
|
||||||
|
list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
|
||||||
|
$thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
|
||||||
|
$thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'mp3gain_album_minmax':
|
||||||
|
list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
|
||||||
|
$thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
|
||||||
|
$thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tracknumber':
|
||||||
|
if (is_array($thisfile_ape_items_current['data'])) {
|
||||||
|
foreach ($thisfile_ape_items_current['data'] as $comment) {
|
||||||
|
$thisfile_ape['comments']['track'][] = $comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'cover art (artist)':
|
||||||
|
case 'cover art (back)':
|
||||||
|
case 'cover art (band logo)':
|
||||||
|
case 'cover art (band)':
|
||||||
|
case 'cover art (colored fish)':
|
||||||
|
case 'cover art (composer)':
|
||||||
|
case 'cover art (conductor)':
|
||||||
|
case 'cover art (front)':
|
||||||
|
case 'cover art (icon)':
|
||||||
|
case 'cover art (illustration)':
|
||||||
|
case 'cover art (lead)':
|
||||||
|
case 'cover art (leaflet)':
|
||||||
|
case 'cover art (lyricist)':
|
||||||
|
case 'cover art (media)':
|
||||||
|
case 'cover art (movie scene)':
|
||||||
|
case 'cover art (other icon)':
|
||||||
|
case 'cover art (other)':
|
||||||
|
case 'cover art (performance)':
|
||||||
|
case 'cover art (publisher logo)':
|
||||||
|
case 'cover art (recording)':
|
||||||
|
case 'cover art (studio)':
|
||||||
|
// list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
|
||||||
|
list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
|
||||||
|
$thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
|
||||||
|
$thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
|
||||||
|
|
||||||
|
$thisfile_ape_items_current['image_mime'] = '';
|
||||||
|
$imageinfo = array();
|
||||||
|
$imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
|
||||||
|
$thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
|
||||||
|
|
||||||
|
do {
|
||||||
|
if ($this->inline_attachments === false) {
|
||||||
|
// skip entirely
|
||||||
|
unset($thisfile_ape_items_current['data']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($this->inline_attachments === true) {
|
||||||
|
// great
|
||||||
|
} elseif (is_int($this->inline_attachments)) {
|
||||||
|
if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
|
||||||
|
// too big, skip
|
||||||
|
$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)';
|
||||||
|
unset($thisfile_ape_items_current['data']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} elseif (is_string($this->inline_attachments)) {
|
||||||
|
$this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
|
||||||
|
if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
|
||||||
|
// cannot write, skip
|
||||||
|
$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
|
||||||
|
unset($thisfile_ape_items_current['data']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we get this far, must be OK
|
||||||
|
if (is_string($this->inline_attachments)) {
|
||||||
|
$destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
|
||||||
|
if (!file_exists($destination_filename) || is_writable($destination_filename)) {
|
||||||
|
file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
|
||||||
|
} else {
|
||||||
|
$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
|
||||||
|
}
|
||||||
|
$thisfile_ape_items_current['data_filename'] = $destination_filename;
|
||||||
|
unset($thisfile_ape_items_current['data']);
|
||||||
|
} else {
|
||||||
|
if (!isset($info['ape']['comments']['picture'])) {
|
||||||
|
$info['ape']['comments']['picture'] = array();
|
||||||
|
}
|
||||||
|
$info['ape']['comments']['picture'][] = array('data'=>$thisfile_ape_items_current['data'], 'image_mime'=>$thisfile_ape_items_current['image_mime']);
|
||||||
|
}
|
||||||
|
} while (false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (is_array($thisfile_ape_items_current['data'])) {
|
||||||
|
foreach ($thisfile_ape_items_current['data'] as $comment) {
|
||||||
|
$thisfile_ape['comments'][strtolower($item_key)][] = $comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (empty($thisfile_replaygain)) {
|
||||||
|
unset($info['replay_gain']);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAPEheaderFooter($APEheaderFooterData) {
|
||||||
|
// http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
|
||||||
|
|
||||||
|
// shortcut
|
||||||
|
$headerfooterinfo['raw'] = array();
|
||||||
|
$headerfooterinfo_raw = &$headerfooterinfo['raw'];
|
||||||
|
|
||||||
|
$headerfooterinfo_raw['footer_tag'] = substr($APEheaderFooterData, 0, 8);
|
||||||
|
if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$headerfooterinfo_raw['version'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 8, 4));
|
||||||
|
$headerfooterinfo_raw['tagsize'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4));
|
||||||
|
$headerfooterinfo_raw['tag_items'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4));
|
||||||
|
$headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4));
|
||||||
|
$headerfooterinfo_raw['reserved'] = substr($APEheaderFooterData, 24, 8);
|
||||||
|
|
||||||
|
$headerfooterinfo['tag_version'] = $headerfooterinfo_raw['version'] / 1000;
|
||||||
|
if ($headerfooterinfo['tag_version'] >= 2) {
|
||||||
|
$headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']);
|
||||||
|
}
|
||||||
|
return $headerfooterinfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAPEtagFlags($rawflagint) {
|
||||||
|
// "Note: APE Tags 1.0 do not use any of the APE Tag flags.
|
||||||
|
// All are set to zero on creation and ignored on reading."
|
||||||
|
// http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html
|
||||||
|
$flags['header'] = (bool) ($rawflagint & 0x80000000);
|
||||||
|
$flags['footer'] = (bool) ($rawflagint & 0x40000000);
|
||||||
|
$flags['this_is_header'] = (bool) ($rawflagint & 0x20000000);
|
||||||
|
$flags['item_contents_raw'] = ($rawflagint & 0x00000006) >> 1;
|
||||||
|
$flags['read_only'] = (bool) ($rawflagint & 0x00000001);
|
||||||
|
|
||||||
|
$flags['item_contents'] = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']);
|
||||||
|
|
||||||
|
return $flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
function APEcontentTypeFlagLookup($contenttypeid) {
|
||||||
|
static $APEcontentTypeFlagLookup = array(
|
||||||
|
0 => 'utf-8',
|
||||||
|
1 => 'binary',
|
||||||
|
2 => 'external',
|
||||||
|
3 => 'reserved'
|
||||||
|
);
|
||||||
|
return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
function APEtagItemIsUTF8Lookup($itemkey) {
|
||||||
|
static $APEtagItemIsUTF8Lookup = array(
|
||||||
|
'title',
|
||||||
|
'subtitle',
|
||||||
|
'artist',
|
||||||
|
'album',
|
||||||
|
'debut album',
|
||||||
|
'publisher',
|
||||||
|
'conductor',
|
||||||
|
'track',
|
||||||
|
'composer',
|
||||||
|
'comment',
|
||||||
|
'copyright',
|
||||||
|
'publicationright',
|
||||||
|
'file',
|
||||||
|
'year',
|
||||||
|
'record date',
|
||||||
|
'record location',
|
||||||
|
'genre',
|
||||||
|
'media',
|
||||||
|
'related',
|
||||||
|
'isrc',
|
||||||
|
'abstract',
|
||||||
|
'language',
|
||||||
|
'bibliography'
|
||||||
|
);
|
||||||
|
return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
362
app/library/getid3/module.tag.id3v1.php
Normal file
362
app/library/getid3/module.tag.id3v1.php
Normal file
|
@ -0,0 +1,362 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.tag.id3v1.php //
|
||||||
|
// module for analyzing ID3v1 tags //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_id3v1 extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
if (!getid3_lib::intValueSupported($info['filesize'])) {
|
||||||
|
$info['warning'][] = 'Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, -256, SEEK_END);
|
||||||
|
$preid3v1 = fread($this->getid3->fp, 128);
|
||||||
|
$id3v1tag = fread($this->getid3->fp, 128);
|
||||||
|
|
||||||
|
if (substr($id3v1tag, 0, 3) == 'TAG') {
|
||||||
|
|
||||||
|
$info['avdataend'] = $info['filesize'] - 128;
|
||||||
|
|
||||||
|
$ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30));
|
||||||
|
$ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30));
|
||||||
|
$ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30));
|
||||||
|
$ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4));
|
||||||
|
$ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them
|
||||||
|
$ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1));
|
||||||
|
|
||||||
|
// If second-last byte of comment field is null and last byte of comment field is non-null
|
||||||
|
// then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
|
||||||
|
if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) {
|
||||||
|
$ParsedID3v1['track'] = ord(substr($ParsedID3v1['comment'], 29, 1));
|
||||||
|
$ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28);
|
||||||
|
}
|
||||||
|
$ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
|
||||||
|
|
||||||
|
$ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
|
||||||
|
if (!empty($ParsedID3v1['genre'])) {
|
||||||
|
unset($ParsedID3v1['genreid']);
|
||||||
|
}
|
||||||
|
if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
|
||||||
|
unset($ParsedID3v1['genre']);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($ParsedID3v1 as $key => $value) {
|
||||||
|
$ParsedID3v1['comments'][$key][0] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
|
||||||
|
$GoodFormatID3v1tag = $this->GenerateID3v1Tag(
|
||||||
|
$ParsedID3v1['title'],
|
||||||
|
$ParsedID3v1['artist'],
|
||||||
|
$ParsedID3v1['album'],
|
||||||
|
$ParsedID3v1['year'],
|
||||||
|
(isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
|
||||||
|
$ParsedID3v1['comment'],
|
||||||
|
(!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : ''));
|
||||||
|
$ParsedID3v1['padding_valid'] = true;
|
||||||
|
if ($id3v1tag !== $GoodFormatID3v1tag) {
|
||||||
|
$ParsedID3v1['padding_valid'] = false;
|
||||||
|
$info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding';
|
||||||
|
}
|
||||||
|
|
||||||
|
$ParsedID3v1['tag_offset_end'] = $info['filesize'];
|
||||||
|
$ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
|
||||||
|
|
||||||
|
$info['id3v1'] = $ParsedID3v1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr($preid3v1, 0, 3) == 'TAG') {
|
||||||
|
// The way iTunes handles tags is, well, brain-damaged.
|
||||||
|
// It completely ignores v1 if ID3v2 is present.
|
||||||
|
// This goes as far as adding a new v1 tag *even if there already is one*
|
||||||
|
|
||||||
|
// A suspected double-ID3v1 tag has been detected, but it could be that
|
||||||
|
// the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
|
||||||
|
if (substr($preid3v1, 96, 8) == 'APETAGEX') {
|
||||||
|
// an APE tag footer was found before the last ID3v1, assume false "TAG" synch
|
||||||
|
} elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
|
||||||
|
// a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
|
||||||
|
} else {
|
||||||
|
// APE and Lyrics3 footers not found - assume double ID3v1
|
||||||
|
$info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes';
|
||||||
|
$info['avdataend'] -= 128;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function cutfield($str) {
|
||||||
|
return trim(substr($str, 0, strcspn($str, "\x00")));
|
||||||
|
}
|
||||||
|
|
||||||
|
static function ArrayOfGenres($allowSCMPXextended=false) {
|
||||||
|
static $GenreLookup = array(
|
||||||
|
0 => 'Blues',
|
||||||
|
1 => 'Classic Rock',
|
||||||
|
2 => 'Country',
|
||||||
|
3 => 'Dance',
|
||||||
|
4 => 'Disco',
|
||||||
|
5 => 'Funk',
|
||||||
|
6 => 'Grunge',
|
||||||
|
7 => 'Hip-Hop',
|
||||||
|
8 => 'Jazz',
|
||||||
|
9 => 'Metal',
|
||||||
|
10 => 'New Age',
|
||||||
|
11 => 'Oldies',
|
||||||
|
12 => 'Other',
|
||||||
|
13 => 'Pop',
|
||||||
|
14 => 'R&B',
|
||||||
|
15 => 'Rap',
|
||||||
|
16 => 'Reggae',
|
||||||
|
17 => 'Rock',
|
||||||
|
18 => 'Techno',
|
||||||
|
19 => 'Industrial',
|
||||||
|
20 => 'Alternative',
|
||||||
|
21 => 'Ska',
|
||||||
|
22 => 'Death Metal',
|
||||||
|
23 => 'Pranks',
|
||||||
|
24 => 'Soundtrack',
|
||||||
|
25 => 'Euro-Techno',
|
||||||
|
26 => 'Ambient',
|
||||||
|
27 => 'Trip-Hop',
|
||||||
|
28 => 'Vocal',
|
||||||
|
29 => 'Jazz+Funk',
|
||||||
|
30 => 'Fusion',
|
||||||
|
31 => 'Trance',
|
||||||
|
32 => 'Classical',
|
||||||
|
33 => 'Instrumental',
|
||||||
|
34 => 'Acid',
|
||||||
|
35 => 'House',
|
||||||
|
36 => 'Game',
|
||||||
|
37 => 'Sound Clip',
|
||||||
|
38 => 'Gospel',
|
||||||
|
39 => 'Noise',
|
||||||
|
40 => 'Alt. Rock',
|
||||||
|
41 => 'Bass',
|
||||||
|
42 => 'Soul',
|
||||||
|
43 => 'Punk',
|
||||||
|
44 => 'Space',
|
||||||
|
45 => 'Meditative',
|
||||||
|
46 => 'Instrumental Pop',
|
||||||
|
47 => 'Instrumental Rock',
|
||||||
|
48 => 'Ethnic',
|
||||||
|
49 => 'Gothic',
|
||||||
|
50 => 'Darkwave',
|
||||||
|
51 => 'Techno-Industrial',
|
||||||
|
52 => 'Electronic',
|
||||||
|
53 => 'Pop-Folk',
|
||||||
|
54 => 'Eurodance',
|
||||||
|
55 => 'Dream',
|
||||||
|
56 => 'Southern Rock',
|
||||||
|
57 => 'Comedy',
|
||||||
|
58 => 'Cult',
|
||||||
|
59 => 'Gangsta Rap',
|
||||||
|
60 => 'Top 40',
|
||||||
|
61 => 'Christian Rap',
|
||||||
|
62 => 'Pop/Funk',
|
||||||
|
63 => 'Jungle',
|
||||||
|
64 => 'Native American',
|
||||||
|
65 => 'Cabaret',
|
||||||
|
66 => 'New Wave',
|
||||||
|
67 => 'Psychedelic',
|
||||||
|
68 => 'Rave',
|
||||||
|
69 => 'Showtunes',
|
||||||
|
70 => 'Trailer',
|
||||||
|
71 => 'Lo-Fi',
|
||||||
|
72 => 'Tribal',
|
||||||
|
73 => 'Acid Punk',
|
||||||
|
74 => 'Acid Jazz',
|
||||||
|
75 => 'Polka',
|
||||||
|
76 => 'Retro',
|
||||||
|
77 => 'Musical',
|
||||||
|
78 => 'Rock & Roll',
|
||||||
|
79 => 'Hard Rock',
|
||||||
|
80 => 'Folk',
|
||||||
|
81 => 'Folk/Rock',
|
||||||
|
82 => 'National Folk',
|
||||||
|
83 => 'Swing',
|
||||||
|
84 => 'Fast-Fusion',
|
||||||
|
85 => 'Bebob',
|
||||||
|
86 => 'Latin',
|
||||||
|
87 => 'Revival',
|
||||||
|
88 => 'Celtic',
|
||||||
|
89 => 'Bluegrass',
|
||||||
|
90 => 'Avantgarde',
|
||||||
|
91 => 'Gothic Rock',
|
||||||
|
92 => 'Progressive Rock',
|
||||||
|
93 => 'Psychedelic Rock',
|
||||||
|
94 => 'Symphonic Rock',
|
||||||
|
95 => 'Slow Rock',
|
||||||
|
96 => 'Big Band',
|
||||||
|
97 => 'Chorus',
|
||||||
|
98 => 'Easy Listening',
|
||||||
|
99 => 'Acoustic',
|
||||||
|
100 => 'Humour',
|
||||||
|
101 => 'Speech',
|
||||||
|
102 => 'Chanson',
|
||||||
|
103 => 'Opera',
|
||||||
|
104 => 'Chamber Music',
|
||||||
|
105 => 'Sonata',
|
||||||
|
106 => 'Symphony',
|
||||||
|
107 => 'Booty Bass',
|
||||||
|
108 => 'Primus',
|
||||||
|
109 => 'Porn Groove',
|
||||||
|
110 => 'Satire',
|
||||||
|
111 => 'Slow Jam',
|
||||||
|
112 => 'Club',
|
||||||
|
113 => 'Tango',
|
||||||
|
114 => 'Samba',
|
||||||
|
115 => 'Folklore',
|
||||||
|
116 => 'Ballad',
|
||||||
|
117 => 'Power Ballad',
|
||||||
|
118 => 'Rhythmic Soul',
|
||||||
|
119 => 'Freestyle',
|
||||||
|
120 => 'Duet',
|
||||||
|
121 => 'Punk Rock',
|
||||||
|
122 => 'Drum Solo',
|
||||||
|
123 => 'A Cappella',
|
||||||
|
124 => 'Euro-House',
|
||||||
|
125 => 'Dance Hall',
|
||||||
|
126 => 'Goa',
|
||||||
|
127 => 'Drum & Bass',
|
||||||
|
128 => 'Club-House',
|
||||||
|
129 => 'Hardcore',
|
||||||
|
130 => 'Terror',
|
||||||
|
131 => 'Indie',
|
||||||
|
132 => 'BritPop',
|
||||||
|
133 => 'Negerpunk',
|
||||||
|
134 => 'Polsk Punk',
|
||||||
|
135 => 'Beat',
|
||||||
|
136 => 'Christian Gangsta Rap',
|
||||||
|
137 => 'Heavy Metal',
|
||||||
|
138 => 'Black Metal',
|
||||||
|
139 => 'Crossover',
|
||||||
|
140 => 'Contemporary Christian',
|
||||||
|
141 => 'Christian Rock',
|
||||||
|
142 => 'Merengue',
|
||||||
|
143 => 'Salsa',
|
||||||
|
144 => 'Thrash Metal',
|
||||||
|
145 => 'Anime',
|
||||||
|
146 => 'JPop',
|
||||||
|
147 => 'Synthpop',
|
||||||
|
|
||||||
|
255 => 'Unknown',
|
||||||
|
|
||||||
|
'CR' => 'Cover',
|
||||||
|
'RX' => 'Remix'
|
||||||
|
);
|
||||||
|
|
||||||
|
static $GenreLookupSCMPX = array();
|
||||||
|
if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
|
||||||
|
$GenreLookupSCMPX = $GenreLookup;
|
||||||
|
// http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
|
||||||
|
// Extended ID3v1 genres invented by SCMPX
|
||||||
|
// Note that 255 "Japanese Anime" conflicts with standard "Unknown"
|
||||||
|
$GenreLookupSCMPX[240] = 'Sacred';
|
||||||
|
$GenreLookupSCMPX[241] = 'Northern Europe';
|
||||||
|
$GenreLookupSCMPX[242] = 'Irish & Scottish';
|
||||||
|
$GenreLookupSCMPX[243] = 'Scotland';
|
||||||
|
$GenreLookupSCMPX[244] = 'Ethnic Europe';
|
||||||
|
$GenreLookupSCMPX[245] = 'Enka';
|
||||||
|
$GenreLookupSCMPX[246] = 'Children\'s Song';
|
||||||
|
$GenreLookupSCMPX[247] = 'Japanese Sky';
|
||||||
|
$GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
|
||||||
|
$GenreLookupSCMPX[249] = 'Japanese Doom Rock';
|
||||||
|
$GenreLookupSCMPX[250] = 'Japanese J-POP';
|
||||||
|
$GenreLookupSCMPX[251] = 'Japanese Seiyu';
|
||||||
|
$GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
|
||||||
|
$GenreLookupSCMPX[253] = 'Japanese Moemoe';
|
||||||
|
$GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
|
||||||
|
//$GenreLookupSCMPX[255] = 'Japanese Anime';
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function LookupGenreName($genreid, $allowSCMPXextended=true) {
|
||||||
|
switch ($genreid) {
|
||||||
|
case 'RX':
|
||||||
|
case 'CR':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!is_numeric($genreid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$genreid = intval($genreid); // to handle 3 or '3' or '03'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$GenreLookup = getid3_id3v1::ArrayOfGenres($allowSCMPXextended);
|
||||||
|
return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function LookupGenreID($genre, $allowSCMPXextended=false) {
|
||||||
|
$GenreLookup = getid3_id3v1::ArrayOfGenres($allowSCMPXextended);
|
||||||
|
$LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
|
||||||
|
foreach ($GenreLookup as $key => $value) {
|
||||||
|
if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function StandardiseID3v1GenreName($OriginalGenre) {
|
||||||
|
if (($GenreID = getid3_id3v1::LookupGenreID($OriginalGenre)) !== false) {
|
||||||
|
return getid3_id3v1::LookupGenreName($GenreID);
|
||||||
|
}
|
||||||
|
return $OriginalGenre;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
|
||||||
|
$ID3v1Tag = 'TAG';
|
||||||
|
$ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
|
||||||
|
$ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
|
||||||
|
$ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
|
||||||
|
$ID3v1Tag .= str_pad(trim(substr($year, 0, 4)), 4, "\x00", STR_PAD_LEFT);
|
||||||
|
if (!empty($track) && ($track > 0) && ($track <= 255)) {
|
||||||
|
$ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
|
||||||
|
$ID3v1Tag .= "\x00";
|
||||||
|
if (gettype($track) == 'string') {
|
||||||
|
$track = (int) $track;
|
||||||
|
}
|
||||||
|
$ID3v1Tag .= chr($track);
|
||||||
|
} else {
|
||||||
|
$ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
|
||||||
|
}
|
||||||
|
if (($genreid < 0) || ($genreid > 147)) {
|
||||||
|
$genreid = 255; // 'unknown' genre
|
||||||
|
}
|
||||||
|
switch (gettype($genreid)) {
|
||||||
|
case 'string':
|
||||||
|
case 'integer':
|
||||||
|
$ID3v1Tag .= chr(intval($genreid));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$ID3v1Tag .= chr(255); // 'unknown' genre
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ID3v1Tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
3327
app/library/getid3/module.tag.id3v2.php
Normal file
3327
app/library/getid3/module.tag.id3v2.php
Normal file
File diff suppressed because it is too large
Load diff
297
app/library/getid3/module.tag.lyrics3.php
Normal file
297
app/library/getid3/module.tag.lyrics3.php
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// //
|
||||||
|
// module.tag.lyrics3.php //
|
||||||
|
// module for analyzing Lyrics3 tags //
|
||||||
|
// dependencies: module.tag.apetag.php (optional) //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_lyrics3 extends getid3_handler
|
||||||
|
{
|
||||||
|
|
||||||
|
function Analyze() {
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
// http://www.volweb.cz/str/tags.htm
|
||||||
|
|
||||||
|
if (!getid3_lib::intValueSupported($info['filesize'])) {
|
||||||
|
$info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, (0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size]
|
||||||
|
$lyrics3_id3v1 = fread($this->getid3->fp, 128 + 9 + 6);
|
||||||
|
$lyrics3lsz = substr($lyrics3_id3v1, 0, 6); // Lyrics3size
|
||||||
|
$lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200
|
||||||
|
$id3v1tag = substr($lyrics3_id3v1, 15, 128); // ID3v1
|
||||||
|
|
||||||
|
if ($lyrics3end == 'LYRICSEND') {
|
||||||
|
// Lyrics3v1, ID3v1, no APE
|
||||||
|
|
||||||
|
$lyrics3size = 5100;
|
||||||
|
$lyrics3offset = $info['filesize'] - 128 - $lyrics3size;
|
||||||
|
$lyrics3version = 1;
|
||||||
|
|
||||||
|
} elseif ($lyrics3end == 'LYRICS200') {
|
||||||
|
// Lyrics3v2, ID3v1, no APE
|
||||||
|
|
||||||
|
// LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
|
||||||
|
$lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200');
|
||||||
|
$lyrics3offset = $info['filesize'] - 128 - $lyrics3size;
|
||||||
|
$lyrics3version = 2;
|
||||||
|
|
||||||
|
} elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) {
|
||||||
|
// Lyrics3v1, no ID3v1, no APE
|
||||||
|
|
||||||
|
$lyrics3size = 5100;
|
||||||
|
$lyrics3offset = $info['filesize'] - $lyrics3size;
|
||||||
|
$lyrics3version = 1;
|
||||||
|
$lyrics3offset = $info['filesize'] - $lyrics3size;
|
||||||
|
|
||||||
|
} elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICS200')) {
|
||||||
|
|
||||||
|
// Lyrics3v2, no ID3v1, no APE
|
||||||
|
|
||||||
|
$lyrics3size = strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
|
||||||
|
$lyrics3offset = $info['filesize'] - $lyrics3size;
|
||||||
|
$lyrics3version = 2;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (isset($info['ape']['tag_offset_start']) && ($info['ape']['tag_offset_start'] > 15)) {
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $info['ape']['tag_offset_start'] - 15, SEEK_SET);
|
||||||
|
$lyrics3lsz = fread($this->getid3->fp, 6);
|
||||||
|
$lyrics3end = fread($this->getid3->fp, 9);
|
||||||
|
|
||||||
|
if ($lyrics3end == 'LYRICSEND') {
|
||||||
|
// Lyrics3v1, APE, maybe ID3v1
|
||||||
|
|
||||||
|
$lyrics3size = 5100;
|
||||||
|
$lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size;
|
||||||
|
$info['avdataend'] = $lyrics3offset;
|
||||||
|
$lyrics3version = 1;
|
||||||
|
$info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability';
|
||||||
|
|
||||||
|
} elseif ($lyrics3end == 'LYRICS200') {
|
||||||
|
// Lyrics3v2, APE, maybe ID3v1
|
||||||
|
|
||||||
|
$lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
|
||||||
|
$lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size;
|
||||||
|
$lyrics3version = 2;
|
||||||
|
$info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($lyrics3offset)) {
|
||||||
|
$info['avdataend'] = $lyrics3offset;
|
||||||
|
$this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size);
|
||||||
|
|
||||||
|
if (!isset($info['ape'])) {
|
||||||
|
$GETID3_ERRORARRAY = &$info['warning'];
|
||||||
|
if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, false)) {
|
||||||
|
$getid3_temp = new getID3();
|
||||||
|
$getid3_temp->openfile($this->getid3->filename);
|
||||||
|
$getid3_apetag = new getid3_apetag($getid3_temp);
|
||||||
|
$getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start'];
|
||||||
|
$getid3_apetag->Analyze();
|
||||||
|
if (!empty($getid3_temp->info['ape'])) {
|
||||||
|
$info['ape'] = $getid3_temp->info['ape'];
|
||||||
|
}
|
||||||
|
if (!empty($getid3_temp->info['replay_gain'])) {
|
||||||
|
$info['replay_gain'] = $getid3_temp->info['replay_gain'];
|
||||||
|
}
|
||||||
|
unset($getid3_temp, $getid3_apetag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLyrics3Data($endoffset, $version, $length) {
|
||||||
|
// http://www.volweb.cz/str/tags.htm
|
||||||
|
|
||||||
|
$info = &$this->getid3->info;
|
||||||
|
|
||||||
|
if (!getid3_lib::intValueSupported($endoffset)) {
|
||||||
|
$info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek($this->getid3->fp, $endoffset, SEEK_SET);
|
||||||
|
if ($length <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$rawdata = fread($this->getid3->fp, $length);
|
||||||
|
|
||||||
|
$ParsedLyrics3['raw']['lyrics3version'] = $version;
|
||||||
|
$ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
|
||||||
|
$ParsedLyrics3['tag_offset_start'] = $endoffset;
|
||||||
|
$ParsedLyrics3['tag_offset_end'] = $endoffset + $length - 1;
|
||||||
|
|
||||||
|
if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') {
|
||||||
|
if (strpos($rawdata, 'LYRICSBEGIN') !== false) {
|
||||||
|
|
||||||
|
$info['warning'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version;
|
||||||
|
$info['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN');
|
||||||
|
$rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN'));
|
||||||
|
$length = strlen($rawdata);
|
||||||
|
$ParsedLyrics3['tag_offset_start'] = $info['avdataend'];
|
||||||
|
$ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$info['error'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead';
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($version) {
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICSEND') {
|
||||||
|
$ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9));
|
||||||
|
$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
|
||||||
|
} else {
|
||||||
|
$info['error'][] = '"LYRICSEND" expected at '.(ftell($this->getid3->fp) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICS200') {
|
||||||
|
$ParsedLyrics3['raw']['unparsed'] = substr($rawdata, 11, strlen($rawdata) - 11 - 9 - 6); // LYRICSBEGIN + LYRICS200 + LSZ
|
||||||
|
$rawdata = $ParsedLyrics3['raw']['unparsed'];
|
||||||
|
while (strlen($rawdata) > 0) {
|
||||||
|
$fieldname = substr($rawdata, 0, 3);
|
||||||
|
$fieldsize = (int) substr($rawdata, 3, 5);
|
||||||
|
$ParsedLyrics3['raw'][$fieldname] = substr($rawdata, 8, $fieldsize);
|
||||||
|
$rawdata = substr($rawdata, 3 + 5 + $fieldsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($ParsedLyrics3['raw']['IND'])) {
|
||||||
|
$i = 0;
|
||||||
|
$flagnames = array('lyrics', 'timestamps', 'inhibitrandom');
|
||||||
|
foreach ($flagnames as $flagname) {
|
||||||
|
if (strlen($ParsedLyrics3['raw']['IND']) > $i++) {
|
||||||
|
$ParsedLyrics3['flags'][$flagname] = $this->IntString2Bool(substr($ParsedLyrics3['raw']['IND'], $i, 1 - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldnametranslation = array('ETT'=>'title', 'EAR'=>'artist', 'EAL'=>'album', 'INF'=>'comment', 'AUT'=>'author');
|
||||||
|
foreach ($fieldnametranslation as $key => $value) {
|
||||||
|
if (isset($ParsedLyrics3['raw'][$key])) {
|
||||||
|
$ParsedLyrics3['comments'][$value][] = trim($ParsedLyrics3['raw'][$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($ParsedLyrics3['raw']['IMG'])) {
|
||||||
|
$imagestrings = explode("\r\n", $ParsedLyrics3['raw']['IMG']);
|
||||||
|
foreach ($imagestrings as $key => $imagestring) {
|
||||||
|
if (strpos($imagestring, '||') !== false) {
|
||||||
|
$imagearray = explode('||', $imagestring);
|
||||||
|
$ParsedLyrics3['images'][$key]['filename'] = (isset($imagearray[0]) ? $imagearray[0] : '');
|
||||||
|
$ParsedLyrics3['images'][$key]['description'] = (isset($imagearray[1]) ? $imagearray[1] : '');
|
||||||
|
$ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds(isset($imagearray[2]) ? $imagearray[2] : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($ParsedLyrics3['raw']['LYR'])) {
|
||||||
|
$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$info['error'][] = '"LYRICS200" expected at '.(ftell($this->getid3->fp) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$info['error'][] = 'Cannot process Lyrics3 version '.$version.' (only v1 and v2)';
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] <= $ParsedLyrics3['tag_offset_end'])) {
|
||||||
|
$info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data';
|
||||||
|
unset($info['id3v1']);
|
||||||
|
foreach ($info['warning'] as $key => $value) {
|
||||||
|
if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
|
||||||
|
unset($info['warning'][$key]);
|
||||||
|
sort($info['warning']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$info['lyrics3'] = $ParsedLyrics3;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Lyrics3Timestamp2Seconds($rawtimestamp) {
|
||||||
|
if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) {
|
||||||
|
return (int) (($regs[1] * 60) + $regs[2]);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Lyrics3LyricsTimestampParse(&$Lyrics3data) {
|
||||||
|
$lyricsarray = explode("\r\n", $Lyrics3data['raw']['LYR']);
|
||||||
|
foreach ($lyricsarray as $key => $lyricline) {
|
||||||
|
$regs = array();
|
||||||
|
unset($thislinetimestamps);
|
||||||
|
while (preg_match('#^(\\[[0-9]{2}:[0-9]{2}\\])#', $lyricline, $regs)) {
|
||||||
|
$thislinetimestamps[] = $this->Lyrics3Timestamp2Seconds($regs[0]);
|
||||||
|
$lyricline = str_replace($regs[0], '', $lyricline);
|
||||||
|
}
|
||||||
|
$notimestamplyricsarray[$key] = $lyricline;
|
||||||
|
if (isset($thislinetimestamps) && is_array($thislinetimestamps)) {
|
||||||
|
sort($thislinetimestamps);
|
||||||
|
foreach ($thislinetimestamps as $timestampkey => $timestamp) {
|
||||||
|
if (isset($Lyrics3data['synchedlyrics'][$timestamp])) {
|
||||||
|
// timestamps only have a 1-second resolution, it's possible that multiple lines
|
||||||
|
// could have the same timestamp, if so, append
|
||||||
|
$Lyrics3data['synchedlyrics'][$timestamp] .= "\r\n".$lyricline;
|
||||||
|
} else {
|
||||||
|
$Lyrics3data['synchedlyrics'][$timestamp] = $lyricline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$Lyrics3data['unsynchedlyrics'] = implode("\r\n", $notimestamplyricsarray);
|
||||||
|
if (isset($Lyrics3data['synchedlyrics']) && is_array($Lyrics3data['synchedlyrics'])) {
|
||||||
|
ksort($Lyrics3data['synchedlyrics']);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function IntString2Bool($char) {
|
||||||
|
if ($char == '1') {
|
||||||
|
return true;
|
||||||
|
} elseif ($char == '0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
766
app/library/getid3/module.tag.xmp.php
Normal file
766
app/library/getid3/module.tag.xmp.php
Normal file
|
@ -0,0 +1,766 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// module.tag.xmp.php //
|
||||||
|
// module for analyzing XMP metadata (e.g. in JPEG files) //
|
||||||
|
// dependencies: NONE //
|
||||||
|
// //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// Module originally written [2009-Mar-26] by //
|
||||||
|
// Nigel Barnes <ngbarnesØhotmail*com> //
|
||||||
|
// Bundled into getID3 with permission //
|
||||||
|
// called by getID3 in module.graphic.jpg.php //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**************************************************************************************************
|
||||||
|
* SWISScenter Source Nigel Barnes
|
||||||
|
*
|
||||||
|
* Provides functions for reading information from the 'APP1' Extensible Metadata
|
||||||
|
* Platform (XMP) segment of JPEG format files.
|
||||||
|
* This XMP segment is XML based and contains the Resource Description Framework (RDF)
|
||||||
|
* data, which itself can contain the Dublin Core Metadata Initiative (DCMI) information.
|
||||||
|
*
|
||||||
|
* This code uses segments from the JPEG Metadata Toolkit project by Evan Hunter.
|
||||||
|
*************************************************************************************************/
|
||||||
|
class Image_XMP
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* The name of the image file that contains the XMP fields to extract and modify.
|
||||||
|
* @see Image_XMP()
|
||||||
|
*/
|
||||||
|
var $_sFilename = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
* The XMP fields that were extracted from the image or updated by this class.
|
||||||
|
* @see getAllTags()
|
||||||
|
*/
|
||||||
|
var $_aXMP = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var boolean
|
||||||
|
* True if an APP1 segment was found to contain XMP metadata.
|
||||||
|
* @see isValid()
|
||||||
|
*/
|
||||||
|
var $_bXMPParse = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the status of XMP parsing during instantiation
|
||||||
|
*
|
||||||
|
* You'll normally want to call this method before trying to get XMP fields.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
* Returns true if an APP1 segment was found to contain XMP metadata.
|
||||||
|
*/
|
||||||
|
function isValid()
|
||||||
|
{
|
||||||
|
return $this->_bXMPParse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a copy of all XMP tags extracted from the image
|
||||||
|
*
|
||||||
|
* @return array - An array of XMP fields as it extracted by the XMPparse() function
|
||||||
|
*/
|
||||||
|
function getAllTags()
|
||||||
|
{
|
||||||
|
return $this->_aXMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads all the JPEG header segments from an JPEG image file into an array
|
||||||
|
*
|
||||||
|
* @param string $filename - the filename of the JPEG file to read
|
||||||
|
* @return array $headerdata - Array of JPEG header segments
|
||||||
|
* @return boolean FALSE - if headers could not be read
|
||||||
|
*/
|
||||||
|
function _get_jpeg_header_data($filename)
|
||||||
|
{
|
||||||
|
// prevent refresh from aborting file operations and hosing file
|
||||||
|
ignore_user_abort(true);
|
||||||
|
|
||||||
|
// Attempt to open the jpeg file - the at symbol supresses the error message about
|
||||||
|
// not being able to open files. The file_exists would have been used, but it
|
||||||
|
// does not work with files fetched over http or ftp.
|
||||||
|
if (is_readable($filename) && is_file($filename) && ($filehnd = fopen($filename, 'rb'))) {
|
||||||
|
// great
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the first two characters
|
||||||
|
$data = fread($filehnd, 2);
|
||||||
|
|
||||||
|
// Check that the first two characters are 0xFF 0xD8 (SOI - Start of image)
|
||||||
|
if ($data != "\xFF\xD8")
|
||||||
|
{
|
||||||
|
// No SOI (FF D8) at start of file - This probably isn't a JPEG file - close file and return;
|
||||||
|
echo '<p>This probably is not a JPEG file</p>'."\n";
|
||||||
|
fclose($filehnd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the third character
|
||||||
|
$data = fread($filehnd, 2);
|
||||||
|
|
||||||
|
// Check that the third character is 0xFF (Start of first segment header)
|
||||||
|
if ($data{0} != "\xFF")
|
||||||
|
{
|
||||||
|
// NO FF found - close file and return - JPEG is probably corrupted
|
||||||
|
fclose($filehnd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag that we havent yet hit the compressed image data
|
||||||
|
$hit_compressed_image_data = false;
|
||||||
|
|
||||||
|
// Cycle through the file until, one of: 1) an EOI (End of image) marker is hit,
|
||||||
|
// 2) we have hit the compressed image data (no more headers are allowed after data)
|
||||||
|
// 3) or end of file is hit
|
||||||
|
|
||||||
|
while (($data{1} != "\xD9") && (!$hit_compressed_image_data) && (!feof($filehnd)))
|
||||||
|
{
|
||||||
|
// Found a segment to look at.
|
||||||
|
// Check that the segment marker is not a Restart marker - restart markers don't have size or data after them
|
||||||
|
if ((ord($data{1}) < 0xD0) || (ord($data{1}) > 0xD7))
|
||||||
|
{
|
||||||
|
// Segment isn't a Restart marker
|
||||||
|
// Read the next two bytes (size)
|
||||||
|
$sizestr = fread($filehnd, 2);
|
||||||
|
|
||||||
|
// convert the size bytes to an integer
|
||||||
|
$decodedsize = unpack('nsize', $sizestr);
|
||||||
|
|
||||||
|
// Save the start position of the data
|
||||||
|
$segdatastart = ftell($filehnd);
|
||||||
|
|
||||||
|
// Read the segment data with length indicated by the previously read size
|
||||||
|
$segdata = fread($filehnd, $decodedsize['size'] - 2);
|
||||||
|
|
||||||
|
// Store the segment information in the output array
|
||||||
|
$headerdata[] = array(
|
||||||
|
'SegType' => ord($data{1}),
|
||||||
|
'SegName' => $GLOBALS['JPEG_Segment_Names'][ord($data{1})],
|
||||||
|
'SegDataStart' => $segdatastart,
|
||||||
|
'SegData' => $segdata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a SOS (Start Of Scan) segment, then there is no more header data - the compressed image data follows
|
||||||
|
if ($data{1} == "\xDA")
|
||||||
|
{
|
||||||
|
// Flag that we have hit the compressed image data - exit loop as no more headers available.
|
||||||
|
$hit_compressed_image_data = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not an SOS - Read the next two bytes - should be the segment marker for the next segment
|
||||||
|
$data = fread($filehnd, 2);
|
||||||
|
|
||||||
|
// Check that the first byte of the two is 0xFF as it should be for a marker
|
||||||
|
if ($data{0} != "\xFF")
|
||||||
|
{
|
||||||
|
// NO FF found - close file and return - JPEG is probably corrupted
|
||||||
|
fclose($filehnd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close File
|
||||||
|
fclose($filehnd);
|
||||||
|
// Alow the user to abort from now on
|
||||||
|
ignore_user_abort(false);
|
||||||
|
|
||||||
|
// Return the header data retrieved
|
||||||
|
return $headerdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves XMP information from an APP1 JPEG segment and returns the raw XML text as a string.
|
||||||
|
*
|
||||||
|
* @param string $filename - the filename of the JPEG file to read
|
||||||
|
* @return string $xmp_data - the string of raw XML text
|
||||||
|
* @return boolean FALSE - if an APP 1 XMP segment could not be found, or if an error occured
|
||||||
|
*/
|
||||||
|
function _get_XMP_text($filename)
|
||||||
|
{
|
||||||
|
//Get JPEG header data
|
||||||
|
$jpeg_header_data = $this->_get_jpeg_header_data($filename);
|
||||||
|
|
||||||
|
//Cycle through the header segments
|
||||||
|
for ($i = 0; $i < count($jpeg_header_data); $i++)
|
||||||
|
{
|
||||||
|
// If we find an APP1 header,
|
||||||
|
if (strcmp($jpeg_header_data[$i]['SegName'], 'APP1') == 0)
|
||||||
|
{
|
||||||
|
// And if it has the Adobe XMP/RDF label (http://ns.adobe.com/xap/1.0/\x00) ,
|
||||||
|
if (strncmp($jpeg_header_data[$i]['SegData'], 'http://ns.adobe.com/xap/1.0/'."\x00", 29) == 0)
|
||||||
|
{
|
||||||
|
// Found a XMP/RDF block
|
||||||
|
// Return the XMP text
|
||||||
|
$xmp_data = substr($jpeg_header_data[$i]['SegData'], 29);
|
||||||
|
|
||||||
|
return trim($xmp_data); // trim() should not be neccesary, but some files found in the wild with null-terminated block (known samples from Apple Aperture) causes problems elsewhere (see http://www.getid3.org/phpBB3/viewtopic.php?f=4&t=1153)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a string containing XMP data (XML), and returns an array
|
||||||
|
* which contains all the XMP (XML) information.
|
||||||
|
*
|
||||||
|
* @param string $xml_text - a string containing the XMP data (XML) to be parsed
|
||||||
|
* @return array $xmp_array - an array containing all xmp details retrieved.
|
||||||
|
* @return boolean FALSE - couldn't parse the XMP data
|
||||||
|
*/
|
||||||
|
function read_XMP_array_from_text($xmltext)
|
||||||
|
{
|
||||||
|
// Check if there actually is any text to parse
|
||||||
|
if (trim($xmltext) == '')
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an instance of a xml parser to parse the XML text
|
||||||
|
$xml_parser = xml_parser_create('UTF-8');
|
||||||
|
|
||||||
|
// Change: Fixed problem that caused the whitespace (especially newlines) to be destroyed when converting xml text to an xml array, as of revision 1.10
|
||||||
|
|
||||||
|
// We would like to remove unneccessary white space, but this will also
|
||||||
|
// remove things like newlines (
) in the XML values, so white space
|
||||||
|
// will have to be removed later
|
||||||
|
if (xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, 0) == false)
|
||||||
|
{
|
||||||
|
// Error setting case folding - destroy the parser and return
|
||||||
|
xml_parser_free($xml_parser);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// to use XML code correctly we have to turn case folding
|
||||||
|
// (uppercasing) off. XML is case sensitive and upper
|
||||||
|
// casing is in reality XML standards violation
|
||||||
|
if (xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 0) == false)
|
||||||
|
{
|
||||||
|
// Error setting case folding - destroy the parser and return
|
||||||
|
xml_parser_free($xml_parser);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the XML text into a array structure
|
||||||
|
if (xml_parse_into_struct($xml_parser, $xmltext, $values, $tags) == 0)
|
||||||
|
{
|
||||||
|
// Error Parsing XML - destroy the parser and return
|
||||||
|
xml_parser_free($xml_parser);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy the xml parser
|
||||||
|
xml_parser_free($xml_parser);
|
||||||
|
|
||||||
|
// Clear the output array
|
||||||
|
$xmp_array = array();
|
||||||
|
|
||||||
|
// The XMP data has now been parsed into an array ...
|
||||||
|
|
||||||
|
// Cycle through each of the array elements
|
||||||
|
$current_property = ''; // current property being processed
|
||||||
|
$container_index = -1; // -1 = no container open, otherwise index of container content
|
||||||
|
foreach ($values as $xml_elem)
|
||||||
|
{
|
||||||
|
// Syntax and Class names
|
||||||
|
switch ($xml_elem['tag'])
|
||||||
|
{
|
||||||
|
case 'x:xmpmeta':
|
||||||
|
// only defined attribute is x:xmptk written by Adobe XMP Toolkit; value is the version of the toolkit
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rdf:RDF':
|
||||||
|
// required element immediately within x:xmpmeta; no data here
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rdf:Description':
|
||||||
|
switch ($xml_elem['type'])
|
||||||
|
{
|
||||||
|
case 'open':
|
||||||
|
case 'complete':
|
||||||
|
if (array_key_exists('attributes', $xml_elem))
|
||||||
|
{
|
||||||
|
// rdf:Description may contain wanted attributes
|
||||||
|
foreach (array_keys($xml_elem['attributes']) as $key)
|
||||||
|
{
|
||||||
|
// Check whether we want this details from this attribute
|
||||||
|
if (in_array($key, $GLOBALS['XMP_tag_captions']))
|
||||||
|
{
|
||||||
|
// Attribute wanted
|
||||||
|
$xmp_array[$key] = $xml_elem['attributes'][$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'cdata':
|
||||||
|
case 'close':
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'rdf:ID':
|
||||||
|
case 'rdf:nodeID':
|
||||||
|
// Attributes are ignored
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rdf:li':
|
||||||
|
// Property member
|
||||||
|
if ($xml_elem['type'] == 'complete')
|
||||||
|
{
|
||||||
|
if (array_key_exists('attributes', $xml_elem))
|
||||||
|
{
|
||||||
|
// If Lang Alt (language alternatives) then ensure we take the default language
|
||||||
|
if (isset($xml_elem['attributes']['xml:lang']) && ($xml_elem['attributes']['xml:lang'] != 'x-default'))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($current_property != '')
|
||||||
|
{
|
||||||
|
$xmp_array[$current_property][$container_index] = (isset($xml_elem['value']) ? $xml_elem['value'] : '');
|
||||||
|
$container_index += 1;
|
||||||
|
}
|
||||||
|
//else unidentified attribute!!
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rdf:Seq':
|
||||||
|
case 'rdf:Bag':
|
||||||
|
case 'rdf:Alt':
|
||||||
|
// Container found
|
||||||
|
switch ($xml_elem['type'])
|
||||||
|
{
|
||||||
|
case 'open':
|
||||||
|
$container_index = 0;
|
||||||
|
break;
|
||||||
|
case 'close':
|
||||||
|
$container_index = -1;
|
||||||
|
break;
|
||||||
|
case 'cdata':
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Check whether we want the details from this attribute
|
||||||
|
if (in_array($xml_elem['tag'], $GLOBALS['XMP_tag_captions']))
|
||||||
|
{
|
||||||
|
switch ($xml_elem['type'])
|
||||||
|
{
|
||||||
|
case 'open':
|
||||||
|
// open current element
|
||||||
|
$current_property = $xml_elem['tag'];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'close':
|
||||||
|
// close current element
|
||||||
|
$current_property = '';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'complete':
|
||||||
|
// store attribute value
|
||||||
|
$xmp_array[$xml_elem['tag']] = (isset($xml_elem['value']) ? $xml_elem['value'] : '');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'cdata':
|
||||||
|
// ignore
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return $xmp_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param string - Name of the image file to access and extract XMP information from.
|
||||||
|
*/
|
||||||
|
function Image_XMP($sFilename)
|
||||||
|
{
|
||||||
|
$this->_sFilename = $sFilename;
|
||||||
|
|
||||||
|
if (is_file($this->_sFilename))
|
||||||
|
{
|
||||||
|
// Get XMP data
|
||||||
|
$xmp_data = $this->_get_XMP_text($sFilename);
|
||||||
|
if ($xmp_data)
|
||||||
|
{
|
||||||
|
$this->_aXMP = $this->read_XMP_array_from_text($xmp_data);
|
||||||
|
$this->_bXMPParse = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global Variable: XMP_tag_captions
|
||||||
|
*
|
||||||
|
* The Property names of all known XMP fields.
|
||||||
|
* Note: this is a full list with unrequired properties commented out.
|
||||||
|
*/
|
||||||
|
$GLOBALS['XMP_tag_captions'] = array(
|
||||||
|
// IPTC Core
|
||||||
|
'Iptc4xmpCore:CiAdrCity',
|
||||||
|
'Iptc4xmpCore:CiAdrCtry',
|
||||||
|
'Iptc4xmpCore:CiAdrExtadr',
|
||||||
|
'Iptc4xmpCore:CiAdrPcode',
|
||||||
|
'Iptc4xmpCore:CiAdrRegion',
|
||||||
|
'Iptc4xmpCore:CiEmailWork',
|
||||||
|
'Iptc4xmpCore:CiTelWork',
|
||||||
|
'Iptc4xmpCore:CiUrlWork',
|
||||||
|
'Iptc4xmpCore:CountryCode',
|
||||||
|
'Iptc4xmpCore:CreatorContactInfo',
|
||||||
|
'Iptc4xmpCore:IntellectualGenre',
|
||||||
|
'Iptc4xmpCore:Location',
|
||||||
|
'Iptc4xmpCore:Scene',
|
||||||
|
'Iptc4xmpCore:SubjectCode',
|
||||||
|
// Dublin Core Schema
|
||||||
|
'dc:contributor',
|
||||||
|
'dc:coverage',
|
||||||
|
'dc:creator',
|
||||||
|
'dc:date',
|
||||||
|
'dc:description',
|
||||||
|
'dc:format',
|
||||||
|
'dc:identifier',
|
||||||
|
'dc:language',
|
||||||
|
'dc:publisher',
|
||||||
|
'dc:relation',
|
||||||
|
'dc:rights',
|
||||||
|
'dc:source',
|
||||||
|
'dc:subject',
|
||||||
|
'dc:title',
|
||||||
|
'dc:type',
|
||||||
|
// XMP Basic Schema
|
||||||
|
'xmp:Advisory',
|
||||||
|
'xmp:BaseURL',
|
||||||
|
'xmp:CreateDate',
|
||||||
|
'xmp:CreatorTool',
|
||||||
|
'xmp:Identifier',
|
||||||
|
'xmp:Label',
|
||||||
|
'xmp:MetadataDate',
|
||||||
|
'xmp:ModifyDate',
|
||||||
|
'xmp:Nickname',
|
||||||
|
'xmp:Rating',
|
||||||
|
'xmp:Thumbnails',
|
||||||
|
'xmpidq:Scheme',
|
||||||
|
// XMP Rights Management Schema
|
||||||
|
'xmpRights:Certificate',
|
||||||
|
'xmpRights:Marked',
|
||||||
|
'xmpRights:Owner',
|
||||||
|
'xmpRights:UsageTerms',
|
||||||
|
'xmpRights:WebStatement',
|
||||||
|
// These are not in spec but Photoshop CS seems to use them
|
||||||
|
'xap:Advisory',
|
||||||
|
'xap:BaseURL',
|
||||||
|
'xap:CreateDate',
|
||||||
|
'xap:CreatorTool',
|
||||||
|
'xap:Identifier',
|
||||||
|
'xap:MetadataDate',
|
||||||
|
'xap:ModifyDate',
|
||||||
|
'xap:Nickname',
|
||||||
|
'xap:Rating',
|
||||||
|
'xap:Thumbnails',
|
||||||
|
'xapidq:Scheme',
|
||||||
|
'xapRights:Certificate',
|
||||||
|
'xapRights:Copyright',
|
||||||
|
'xapRights:Marked',
|
||||||
|
'xapRights:Owner',
|
||||||
|
'xapRights:UsageTerms',
|
||||||
|
'xapRights:WebStatement',
|
||||||
|
// XMP Media Management Schema
|
||||||
|
'xapMM:DerivedFrom',
|
||||||
|
'xapMM:DocumentID',
|
||||||
|
'xapMM:History',
|
||||||
|
'xapMM:InstanceID',
|
||||||
|
'xapMM:ManagedFrom',
|
||||||
|
'xapMM:Manager',
|
||||||
|
'xapMM:ManageTo',
|
||||||
|
'xapMM:ManageUI',
|
||||||
|
'xapMM:ManagerVariant',
|
||||||
|
'xapMM:RenditionClass',
|
||||||
|
'xapMM:RenditionParams',
|
||||||
|
'xapMM:VersionID',
|
||||||
|
'xapMM:Versions',
|
||||||
|
'xapMM:LastURL',
|
||||||
|
'xapMM:RenditionOf',
|
||||||
|
'xapMM:SaveID',
|
||||||
|
// XMP Basic Job Ticket Schema
|
||||||
|
'xapBJ:JobRef',
|
||||||
|
// XMP Paged-Text Schema
|
||||||
|
'xmpTPg:MaxPageSize',
|
||||||
|
'xmpTPg:NPages',
|
||||||
|
'xmpTPg:Fonts',
|
||||||
|
'xmpTPg:Colorants',
|
||||||
|
'xmpTPg:PlateNames',
|
||||||
|
// Adobe PDF Schema
|
||||||
|
'pdf:Keywords',
|
||||||
|
'pdf:PDFVersion',
|
||||||
|
'pdf:Producer',
|
||||||
|
// Photoshop Schema
|
||||||
|
'photoshop:AuthorsPosition',
|
||||||
|
'photoshop:CaptionWriter',
|
||||||
|
'photoshop:Category',
|
||||||
|
'photoshop:City',
|
||||||
|
'photoshop:Country',
|
||||||
|
'photoshop:Credit',
|
||||||
|
'photoshop:DateCreated',
|
||||||
|
'photoshop:Headline',
|
||||||
|
'photoshop:History',
|
||||||
|
// Not in XMP spec
|
||||||
|
'photoshop:Instructions',
|
||||||
|
'photoshop:Source',
|
||||||
|
'photoshop:State',
|
||||||
|
'photoshop:SupplementalCategories',
|
||||||
|
'photoshop:TransmissionReference',
|
||||||
|
'photoshop:Urgency',
|
||||||
|
// EXIF Schemas
|
||||||
|
'tiff:ImageWidth',
|
||||||
|
'tiff:ImageLength',
|
||||||
|
'tiff:BitsPerSample',
|
||||||
|
'tiff:Compression',
|
||||||
|
'tiff:PhotometricInterpretation',
|
||||||
|
'tiff:Orientation',
|
||||||
|
'tiff:SamplesPerPixel',
|
||||||
|
'tiff:PlanarConfiguration',
|
||||||
|
'tiff:YCbCrSubSampling',
|
||||||
|
'tiff:YCbCrPositioning',
|
||||||
|
'tiff:XResolution',
|
||||||
|
'tiff:YResolution',
|
||||||
|
'tiff:ResolutionUnit',
|
||||||
|
'tiff:TransferFunction',
|
||||||
|
'tiff:WhitePoint',
|
||||||
|
'tiff:PrimaryChromaticities',
|
||||||
|
'tiff:YCbCrCoefficients',
|
||||||
|
'tiff:ReferenceBlackWhite',
|
||||||
|
'tiff:DateTime',
|
||||||
|
'tiff:ImageDescription',
|
||||||
|
'tiff:Make',
|
||||||
|
'tiff:Model',
|
||||||
|
'tiff:Software',
|
||||||
|
'tiff:Artist',
|
||||||
|
'tiff:Copyright',
|
||||||
|
'exif:ExifVersion',
|
||||||
|
'exif:FlashpixVersion',
|
||||||
|
'exif:ColorSpace',
|
||||||
|
'exif:ComponentsConfiguration',
|
||||||
|
'exif:CompressedBitsPerPixel',
|
||||||
|
'exif:PixelXDimension',
|
||||||
|
'exif:PixelYDimension',
|
||||||
|
'exif:MakerNote',
|
||||||
|
'exif:UserComment',
|
||||||
|
'exif:RelatedSoundFile',
|
||||||
|
'exif:DateTimeOriginal',
|
||||||
|
'exif:DateTimeDigitized',
|
||||||
|
'exif:ExposureTime',
|
||||||
|
'exif:FNumber',
|
||||||
|
'exif:ExposureProgram',
|
||||||
|
'exif:SpectralSensitivity',
|
||||||
|
'exif:ISOSpeedRatings',
|
||||||
|
'exif:OECF',
|
||||||
|
'exif:ShutterSpeedValue',
|
||||||
|
'exif:ApertureValue',
|
||||||
|
'exif:BrightnessValue',
|
||||||
|
'exif:ExposureBiasValue',
|
||||||
|
'exif:MaxApertureValue',
|
||||||
|
'exif:SubjectDistance',
|
||||||
|
'exif:MeteringMode',
|
||||||
|
'exif:LightSource',
|
||||||
|
'exif:Flash',
|
||||||
|
'exif:FocalLength',
|
||||||
|
'exif:SubjectArea',
|
||||||
|
'exif:FlashEnergy',
|
||||||
|
'exif:SpatialFrequencyResponse',
|
||||||
|
'exif:FocalPlaneXResolution',
|
||||||
|
'exif:FocalPlaneYResolution',
|
||||||
|
'exif:FocalPlaneResolutionUnit',
|
||||||
|
'exif:SubjectLocation',
|
||||||
|
'exif:SensingMethod',
|
||||||
|
'exif:FileSource',
|
||||||
|
'exif:SceneType',
|
||||||
|
'exif:CFAPattern',
|
||||||
|
'exif:CustomRendered',
|
||||||
|
'exif:ExposureMode',
|
||||||
|
'exif:WhiteBalance',
|
||||||
|
'exif:DigitalZoomRatio',
|
||||||
|
'exif:FocalLengthIn35mmFilm',
|
||||||
|
'exif:SceneCaptureType',
|
||||||
|
'exif:GainControl',
|
||||||
|
'exif:Contrast',
|
||||||
|
'exif:Saturation',
|
||||||
|
'exif:Sharpness',
|
||||||
|
'exif:DeviceSettingDescription',
|
||||||
|
'exif:SubjectDistanceRange',
|
||||||
|
'exif:ImageUniqueID',
|
||||||
|
'exif:GPSVersionID',
|
||||||
|
'exif:GPSLatitude',
|
||||||
|
'exif:GPSLongitude',
|
||||||
|
'exif:GPSAltitudeRef',
|
||||||
|
'exif:GPSAltitude',
|
||||||
|
'exif:GPSTimeStamp',
|
||||||
|
'exif:GPSSatellites',
|
||||||
|
'exif:GPSStatus',
|
||||||
|
'exif:GPSMeasureMode',
|
||||||
|
'exif:GPSDOP',
|
||||||
|
'exif:GPSSpeedRef',
|
||||||
|
'exif:GPSSpeed',
|
||||||
|
'exif:GPSTrackRef',
|
||||||
|
'exif:GPSTrack',
|
||||||
|
'exif:GPSImgDirectionRef',
|
||||||
|
'exif:GPSImgDirection',
|
||||||
|
'exif:GPSMapDatum',
|
||||||
|
'exif:GPSDestLatitude',
|
||||||
|
'exif:GPSDestLongitude',
|
||||||
|
'exif:GPSDestBearingRef',
|
||||||
|
'exif:GPSDestBearing',
|
||||||
|
'exif:GPSDestDistanceRef',
|
||||||
|
'exif:GPSDestDistance',
|
||||||
|
'exif:GPSProcessingMethod',
|
||||||
|
'exif:GPSAreaInformation',
|
||||||
|
'exif:GPSDifferential',
|
||||||
|
'stDim:w',
|
||||||
|
'stDim:h',
|
||||||
|
'stDim:unit',
|
||||||
|
'xapGImg:height',
|
||||||
|
'xapGImg:width',
|
||||||
|
'xapGImg:format',
|
||||||
|
'xapGImg:image',
|
||||||
|
'stEvt:action',
|
||||||
|
'stEvt:instanceID',
|
||||||
|
'stEvt:parameters',
|
||||||
|
'stEvt:softwareAgent',
|
||||||
|
'stEvt:when',
|
||||||
|
'stRef:instanceID',
|
||||||
|
'stRef:documentID',
|
||||||
|
'stRef:versionID',
|
||||||
|
'stRef:renditionClass',
|
||||||
|
'stRef:renditionParams',
|
||||||
|
'stRef:manager',
|
||||||
|
'stRef:managerVariant',
|
||||||
|
'stRef:manageTo',
|
||||||
|
'stRef:manageUI',
|
||||||
|
'stVer:comments',
|
||||||
|
'stVer:event',
|
||||||
|
'stVer:modifyDate',
|
||||||
|
'stVer:modifier',
|
||||||
|
'stVer:version',
|
||||||
|
'stJob:name',
|
||||||
|
'stJob:id',
|
||||||
|
'stJob:url',
|
||||||
|
// Exif Flash
|
||||||
|
'exif:Fired',
|
||||||
|
'exif:Return',
|
||||||
|
'exif:Mode',
|
||||||
|
'exif:Function',
|
||||||
|
'exif:RedEyeMode',
|
||||||
|
// Exif OECF/SFR
|
||||||
|
'exif:Columns',
|
||||||
|
'exif:Rows',
|
||||||
|
'exif:Names',
|
||||||
|
'exif:Values',
|
||||||
|
// Exif CFAPattern
|
||||||
|
'exif:Columns',
|
||||||
|
'exif:Rows',
|
||||||
|
'exif:Values',
|
||||||
|
// Exif DeviceSettings
|
||||||
|
'exif:Columns',
|
||||||
|
'exif:Rows',
|
||||||
|
'exif:Settings',
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global Variable: JPEG_Segment_Names
|
||||||
|
*
|
||||||
|
* The names of the JPEG segment markers, indexed by their marker number
|
||||||
|
*/
|
||||||
|
$GLOBALS['JPEG_Segment_Names'] = array(
|
||||||
|
0x01 => 'TEM',
|
||||||
|
0x02 => 'RES',
|
||||||
|
0xC0 => 'SOF0',
|
||||||
|
0xC1 => 'SOF1',
|
||||||
|
0xC2 => 'SOF2',
|
||||||
|
0xC3 => 'SOF4',
|
||||||
|
0xC4 => 'DHT',
|
||||||
|
0xC5 => 'SOF5',
|
||||||
|
0xC6 => 'SOF6',
|
||||||
|
0xC7 => 'SOF7',
|
||||||
|
0xC8 => 'JPG',
|
||||||
|
0xC9 => 'SOF9',
|
||||||
|
0xCA => 'SOF10',
|
||||||
|
0xCB => 'SOF11',
|
||||||
|
0xCC => 'DAC',
|
||||||
|
0xCD => 'SOF13',
|
||||||
|
0xCE => 'SOF14',
|
||||||
|
0xCF => 'SOF15',
|
||||||
|
0xD0 => 'RST0',
|
||||||
|
0xD1 => 'RST1',
|
||||||
|
0xD2 => 'RST2',
|
||||||
|
0xD3 => 'RST3',
|
||||||
|
0xD4 => 'RST4',
|
||||||
|
0xD5 => 'RST5',
|
||||||
|
0xD6 => 'RST6',
|
||||||
|
0xD7 => 'RST7',
|
||||||
|
0xD8 => 'SOI',
|
||||||
|
0xD9 => 'EOI',
|
||||||
|
0xDA => 'SOS',
|
||||||
|
0xDB => 'DQT',
|
||||||
|
0xDC => 'DNL',
|
||||||
|
0xDD => 'DRI',
|
||||||
|
0xDE => 'DHP',
|
||||||
|
0xDF => 'EXP',
|
||||||
|
0xE0 => 'APP0',
|
||||||
|
0xE1 => 'APP1',
|
||||||
|
0xE2 => 'APP2',
|
||||||
|
0xE3 => 'APP3',
|
||||||
|
0xE4 => 'APP4',
|
||||||
|
0xE5 => 'APP5',
|
||||||
|
0xE6 => 'APP6',
|
||||||
|
0xE7 => 'APP7',
|
||||||
|
0xE8 => 'APP8',
|
||||||
|
0xE9 => 'APP9',
|
||||||
|
0xEA => 'APP10',
|
||||||
|
0xEB => 'APP11',
|
||||||
|
0xEC => 'APP12',
|
||||||
|
0xED => 'APP13',
|
||||||
|
0xEE => 'APP14',
|
||||||
|
0xEF => 'APP15',
|
||||||
|
0xF0 => 'JPG0',
|
||||||
|
0xF1 => 'JPG1',
|
||||||
|
0xF2 => 'JPG2',
|
||||||
|
0xF3 => 'JPG3',
|
||||||
|
0xF4 => 'JPG4',
|
||||||
|
0xF5 => 'JPG5',
|
||||||
|
0xF6 => 'JPG6',
|
||||||
|
0xF7 => 'JPG7',
|
||||||
|
0xF8 => 'JPG8',
|
||||||
|
0xF9 => 'JPG9',
|
||||||
|
0xFA => 'JPG10',
|
||||||
|
0xFB => 'JPG11',
|
||||||
|
0xFC => 'JPG12',
|
||||||
|
0xFD => 'JPG13',
|
||||||
|
0xFE => 'COM',
|
||||||
|
);
|
||||||
|
|
||||||
|
?>
|
225
app/library/getid3/write.apetag.php
Normal file
225
app/library/getid3/write.apetag.php
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// write.apetag.php //
|
||||||
|
// module for writing APE tags //
|
||||||
|
// dependencies: module.tag.apetag.php //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true);
|
||||||
|
|
||||||
|
class getid3_write_apetag
|
||||||
|
{
|
||||||
|
|
||||||
|
var $filename;
|
||||||
|
var $tag_data;
|
||||||
|
var $always_preserve_replaygain = true; // ReplayGain / MP3gain tags will be copied from old tag even if not passed in data
|
||||||
|
var $warnings = array(); // any non-critical errors will be stored here
|
||||||
|
var $errors = array(); // any critical errors will be stored here
|
||||||
|
|
||||||
|
function getid3_write_apetag() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function WriteAPEtag() {
|
||||||
|
// NOTE: All data passed to this function must be UTF-8 format
|
||||||
|
|
||||||
|
$getID3 = new getID3;
|
||||||
|
$ThisFileInfo = $getID3->analyze($this->filename);
|
||||||
|
|
||||||
|
if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['lyrics3']['tag_offset_end'])) {
|
||||||
|
if ($ThisFileInfo['ape']['tag_offset_start'] >= $ThisFileInfo['lyrics3']['tag_offset_end']) {
|
||||||
|
// Current APE tag between Lyrics3 and ID3v1/EOF
|
||||||
|
// This break Lyrics3 functionality
|
||||||
|
if (!$this->DeleteAPEtag()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$ThisFileInfo = $getID3->analyze($this->filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->always_preserve_replaygain) {
|
||||||
|
$ReplayGainTagsToPreserve = array('mp3gain_minmax', 'mp3gain_album_minmax', 'mp3gain_undo', 'replaygain_track_peak', 'replaygain_track_gain', 'replaygain_album_peak', 'replaygain_album_gain');
|
||||||
|
foreach ($ReplayGainTagsToPreserve as $rg_key) {
|
||||||
|
if (isset($ThisFileInfo['ape']['items'][strtolower($rg_key)]['data'][0]) && !isset($this->tag_data[strtoupper($rg_key)][0])) {
|
||||||
|
$this->tag_data[strtoupper($rg_key)][0] = $ThisFileInfo['ape']['items'][strtolower($rg_key)]['data'][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($APEtag = $this->GenerateAPEtag()) {
|
||||||
|
if (is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) {
|
||||||
|
$oldignoreuserabort = ignore_user_abort(true);
|
||||||
|
flock($fp, LOCK_EX);
|
||||||
|
|
||||||
|
$PostAPEdataOffset = $ThisFileInfo['avdataend'];
|
||||||
|
if (isset($ThisFileInfo['ape']['tag_offset_end'])) {
|
||||||
|
$PostAPEdataOffset = max($PostAPEdataOffset, $ThisFileInfo['ape']['tag_offset_end']);
|
||||||
|
}
|
||||||
|
if (isset($ThisFileInfo['lyrics3']['tag_offset_start'])) {
|
||||||
|
$PostAPEdataOffset = max($PostAPEdataOffset, $ThisFileInfo['lyrics3']['tag_offset_start']);
|
||||||
|
}
|
||||||
|
fseek($fp, $PostAPEdataOffset, SEEK_SET);
|
||||||
|
$PostAPEdata = '';
|
||||||
|
if ($ThisFileInfo['filesize'] > $PostAPEdataOffset) {
|
||||||
|
$PostAPEdata = fread($fp, $ThisFileInfo['filesize'] - $PostAPEdataOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek($fp, $PostAPEdataOffset, SEEK_SET);
|
||||||
|
if (isset($ThisFileInfo['ape']['tag_offset_start'])) {
|
||||||
|
fseek($fp, $ThisFileInfo['ape']['tag_offset_start'], SEEK_SET);
|
||||||
|
}
|
||||||
|
ftruncate($fp, ftell($fp));
|
||||||
|
fwrite($fp, $APEtag, strlen($APEtag));
|
||||||
|
if (!empty($PostAPEdata)) {
|
||||||
|
fwrite($fp, $PostAPEdata, strlen($PostAPEdata));
|
||||||
|
}
|
||||||
|
flock($fp, LOCK_UN);
|
||||||
|
fclose($fp);
|
||||||
|
ignore_user_abort($oldignoreuserabort);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DeleteAPEtag() {
|
||||||
|
$getID3 = new getID3;
|
||||||
|
$ThisFileInfo = $getID3->analyze($this->filename);
|
||||||
|
if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['ape']['tag_offset_end'])) {
|
||||||
|
if (is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) {
|
||||||
|
|
||||||
|
flock($fp, LOCK_EX);
|
||||||
|
$oldignoreuserabort = ignore_user_abort(true);
|
||||||
|
|
||||||
|
fseek($fp, $ThisFileInfo['ape']['tag_offset_end'], SEEK_SET);
|
||||||
|
$DataAfterAPE = '';
|
||||||
|
if ($ThisFileInfo['filesize'] > $ThisFileInfo['ape']['tag_offset_end']) {
|
||||||
|
$DataAfterAPE = fread($fp, $ThisFileInfo['filesize'] - $ThisFileInfo['ape']['tag_offset_end']);
|
||||||
|
}
|
||||||
|
|
||||||
|
ftruncate($fp, $ThisFileInfo['ape']['tag_offset_start']);
|
||||||
|
fseek($fp, $ThisFileInfo['ape']['tag_offset_start'], SEEK_SET);
|
||||||
|
|
||||||
|
if (!empty($DataAfterAPE)) {
|
||||||
|
fwrite($fp, $DataAfterAPE, strlen($DataAfterAPE));
|
||||||
|
}
|
||||||
|
|
||||||
|
flock($fp, LOCK_UN);
|
||||||
|
fclose($fp);
|
||||||
|
ignore_user_abort($oldignoreuserabort);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GenerateAPEtag() {
|
||||||
|
// NOTE: All data passed to this function must be UTF-8 format
|
||||||
|
|
||||||
|
$items = array();
|
||||||
|
if (!is_array($this->tag_data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach ($this->tag_data as $key => $arrayofvalues) {
|
||||||
|
if (!is_array($arrayofvalues)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$valuestring = '';
|
||||||
|
foreach ($arrayofvalues as $value) {
|
||||||
|
$valuestring .= str_replace("\x00", '', $value)."\x00";
|
||||||
|
}
|
||||||
|
$valuestring = rtrim($valuestring, "\x00");
|
||||||
|
|
||||||
|
// Length of the assigned value in bytes
|
||||||
|
$tagitem = getid3_lib::LittleEndian2String(strlen($valuestring), 4);
|
||||||
|
|
||||||
|
//$tagitem .= $this->GenerateAPEtagFlags(true, true, false, 0, false);
|
||||||
|
$tagitem .= "\x00\x00\x00\x00";
|
||||||
|
|
||||||
|
$tagitem .= $this->CleanAPEtagItemKey($key)."\x00";
|
||||||
|
$tagitem .= $valuestring;
|
||||||
|
|
||||||
|
$items[] = $tagitem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->GenerateAPEtagHeaderFooter($items, true).implode('', $items).$this->GenerateAPEtagHeaderFooter($items, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GenerateAPEtagHeaderFooter(&$items, $isheader=false) {
|
||||||
|
$tagdatalength = 0;
|
||||||
|
foreach ($items as $itemdata) {
|
||||||
|
$tagdatalength += strlen($itemdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
$APEheader = 'APETAGEX';
|
||||||
|
$APEheader .= getid3_lib::LittleEndian2String(2000, 4);
|
||||||
|
$APEheader .= getid3_lib::LittleEndian2String(32 + $tagdatalength, 4);
|
||||||
|
$APEheader .= getid3_lib::LittleEndian2String(count($items), 4);
|
||||||
|
$APEheader .= $this->GenerateAPEtagFlags(true, true, $isheader, 0, false);
|
||||||
|
$APEheader .= str_repeat("\x00", 8);
|
||||||
|
|
||||||
|
return $APEheader;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GenerateAPEtagFlags($header=true, $footer=true, $isheader=false, $encodingid=0, $readonly=false) {
|
||||||
|
$APEtagFlags = array_fill(0, 4, 0);
|
||||||
|
if ($header) {
|
||||||
|
$APEtagFlags[0] |= 0x80; // Tag contains a header
|
||||||
|
}
|
||||||
|
if (!$footer) {
|
||||||
|
$APEtagFlags[0] |= 0x40; // Tag contains no footer
|
||||||
|
}
|
||||||
|
if ($isheader) {
|
||||||
|
$APEtagFlags[0] |= 0x20; // This is the header, not the footer
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0: Item contains text information coded in UTF-8
|
||||||
|
// 1: Item contains binary information °)
|
||||||
|
// 2: Item is a locator of external stored information °°)
|
||||||
|
// 3: reserved
|
||||||
|
$APEtagFlags[3] |= ($encodingid << 1);
|
||||||
|
|
||||||
|
if ($readonly) {
|
||||||
|
$APEtagFlags[3] |= 0x01; // Tag or Item is Read Only
|
||||||
|
}
|
||||||
|
|
||||||
|
return chr($APEtagFlags[3]).chr($APEtagFlags[2]).chr($APEtagFlags[1]).chr($APEtagFlags[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CleanAPEtagItemKey($itemkey) {
|
||||||
|
$itemkey = preg_replace("#[^\x20-\x7E]#i", '', $itemkey);
|
||||||
|
|
||||||
|
// http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html
|
||||||
|
switch (strtoupper($itemkey)) {
|
||||||
|
case 'EAN/UPC':
|
||||||
|
case 'ISBN':
|
||||||
|
case 'LC':
|
||||||
|
case 'ISRC':
|
||||||
|
$itemkey = strtoupper($itemkey);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$itemkey = ucwords($itemkey);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return $itemkey;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
138
app/library/getid3/write.id3v1.php
Normal file
138
app/library/getid3/write.id3v1.php
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// write.id3v1.php //
|
||||||
|
// module for writing ID3v1 tags //
|
||||||
|
// dependencies: module.tag.id3v1.php //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
|
||||||
|
|
||||||
|
class getid3_write_id3v1
|
||||||
|
{
|
||||||
|
var $filename;
|
||||||
|
var $filesize;
|
||||||
|
var $tag_data;
|
||||||
|
var $warnings = array(); // any non-critical errors will be stored here
|
||||||
|
var $errors = array(); // any critical errors will be stored here
|
||||||
|
|
||||||
|
function getid3_write_id3v1() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function WriteID3v1() {
|
||||||
|
// File MUST be writeable - CHMOD(646) at least
|
||||||
|
if (!empty($this->filename) && is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename)) {
|
||||||
|
$this->setRealFileSize();
|
||||||
|
if (($this->filesize <= 0) || !getid3_lib::intValueSupported($this->filesize)) {
|
||||||
|
$this->errors[] = 'Unable to WriteID3v1('.$this->filename.') because filesize ('.$this->filesize.') is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($fp_source = fopen($this->filename, 'r+b')) {
|
||||||
|
fseek($fp_source, -128, SEEK_END);
|
||||||
|
if (fread($fp_source, 3) == 'TAG') {
|
||||||
|
fseek($fp_source, -128, SEEK_END); // overwrite existing ID3v1 tag
|
||||||
|
} else {
|
||||||
|
fseek($fp_source, 0, SEEK_END); // append new ID3v1 tag
|
||||||
|
}
|
||||||
|
$this->tag_data['track'] = (isset($this->tag_data['track']) ? $this->tag_data['track'] : (isset($this->tag_data['track_number']) ? $this->tag_data['track_number'] : (isset($this->tag_data['tracknumber']) ? $this->tag_data['tracknumber'] : '')));
|
||||||
|
|
||||||
|
$new_id3v1_tag_data = getid3_id3v1::GenerateID3v1Tag(
|
||||||
|
(isset($this->tag_data['title'] ) ? $this->tag_data['title'] : ''),
|
||||||
|
(isset($this->tag_data['artist'] ) ? $this->tag_data['artist'] : ''),
|
||||||
|
(isset($this->tag_data['album'] ) ? $this->tag_data['album'] : ''),
|
||||||
|
(isset($this->tag_data['year'] ) ? $this->tag_data['year'] : ''),
|
||||||
|
(isset($this->tag_data['genreid']) ? $this->tag_data['genreid'] : ''),
|
||||||
|
(isset($this->tag_data['comment']) ? $this->tag_data['comment'] : ''),
|
||||||
|
(isset($this->tag_data['track'] ) ? $this->tag_data['track'] : ''));
|
||||||
|
fwrite($fp_source, $new_id3v1_tag_data, 128);
|
||||||
|
fclose($fp_source);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'Could not fopen('.$this->filename.', "r+b")';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->errors[] = 'File is not writeable: '.$this->filename;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FixID3v1Padding() {
|
||||||
|
// ID3v1 data is supposed to be padded with NULL characters, but some taggers incorrectly use spaces
|
||||||
|
// This function rewrites the ID3v1 tag with correct padding
|
||||||
|
|
||||||
|
// Initialize getID3 engine
|
||||||
|
$getID3 = new getID3;
|
||||||
|
$getID3->option_tag_id3v2 = false;
|
||||||
|
$getID3->option_tag_apetag = false;
|
||||||
|
$getID3->option_tags_html = false;
|
||||||
|
$getID3->option_extra_info = false;
|
||||||
|
$getID3->option_tag_id3v1 = true;
|
||||||
|
$ThisFileInfo = $getID3->analyze($this->filename);
|
||||||
|
if (isset($ThisFileInfo['tags']['id3v1'])) {
|
||||||
|
foreach ($ThisFileInfo['tags']['id3v1'] as $key => $value) {
|
||||||
|
$id3v1data[$key] = implode(',', $value);
|
||||||
|
}
|
||||||
|
$this->tag_data = $id3v1data;
|
||||||
|
return $this->WriteID3v1();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RemoveID3v1() {
|
||||||
|
// File MUST be writeable - CHMOD(646) at least
|
||||||
|
if (!empty($this->filename) && is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename)) {
|
||||||
|
$this->setRealFileSize();
|
||||||
|
if (($this->filesize <= 0) || !getid3_lib::intValueSupported($this->filesize)) {
|
||||||
|
$this->errors[] = 'Unable to RemoveID3v1('.$this->filename.') because filesize ('.$this->filesize.') is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($fp_source = fopen($this->filename, 'r+b')) {
|
||||||
|
|
||||||
|
fseek($fp_source, -128, SEEK_END);
|
||||||
|
if (fread($fp_source, 3) == 'TAG') {
|
||||||
|
ftruncate($fp_source, $this->filesize - 128);
|
||||||
|
} else {
|
||||||
|
// no ID3v1 tag to begin with - do nothing
|
||||||
|
}
|
||||||
|
fclose($fp_source);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'Could not fopen('.$this->filename.', "r+b")';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->errors[] = $this->filename.' is not writeable';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRealFileSize() {
|
||||||
|
if (PHP_INT_MAX > 2147483647) {
|
||||||
|
$this->filesize = filesize($this->filename);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 32-bit PHP will not return correct values for filesize() if file is >=2GB
|
||||||
|
// but getID3->analyze() has workarounds to get actual filesize
|
||||||
|
$getID3 = new getID3;
|
||||||
|
$getID3->option_tag_id3v1 = false;
|
||||||
|
$getID3->option_tag_id3v2 = false;
|
||||||
|
$getID3->option_tag_apetag = false;
|
||||||
|
$getID3->option_tags_html = false;
|
||||||
|
$getID3->option_extra_info = false;
|
||||||
|
$ThisFileInfo = $getID3->analyze($this->filename);
|
||||||
|
$this->filesize = $ThisFileInfo['filesize'];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
2050
app/library/getid3/write.id3v2.php
Normal file
2050
app/library/getid3/write.id3v2.php
Normal file
File diff suppressed because it is too large
Load diff
73
app/library/getid3/write.lyrics3.php
Normal file
73
app/library/getid3/write.lyrics3.php
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// write.lyrics3.php //
|
||||||
|
// module for writing Lyrics3 tags //
|
||||||
|
// dependencies: module.tag.lyrics3.php //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_write_lyrics3
|
||||||
|
{
|
||||||
|
var $filename;
|
||||||
|
var $tag_data;
|
||||||
|
//var $lyrics3_version = 2; // 1 or 2
|
||||||
|
var $warnings = array(); // any non-critical errors will be stored here
|
||||||
|
var $errors = array(); // any critical errors will be stored here
|
||||||
|
|
||||||
|
function getid3_write_lyrics3() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function WriteLyrics3() {
|
||||||
|
$this->errors[] = 'WriteLyrics3() not yet functional - cannot write Lyrics3';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function DeleteLyrics3() {
|
||||||
|
// Initialize getID3 engine
|
||||||
|
$getID3 = new getID3;
|
||||||
|
$ThisFileInfo = $getID3->analyze($this->filename);
|
||||||
|
if (isset($ThisFileInfo['lyrics3']['tag_offset_start']) && isset($ThisFileInfo['lyrics3']['tag_offset_end'])) {
|
||||||
|
if (is_readable($this->filename) && is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) {
|
||||||
|
|
||||||
|
flock($fp, LOCK_EX);
|
||||||
|
$oldignoreuserabort = ignore_user_abort(true);
|
||||||
|
|
||||||
|
fseek($fp, $ThisFileInfo['lyrics3']['tag_offset_end'], SEEK_SET);
|
||||||
|
$DataAfterLyrics3 = '';
|
||||||
|
if ($ThisFileInfo['filesize'] > $ThisFileInfo['lyrics3']['tag_offset_end']) {
|
||||||
|
$DataAfterLyrics3 = fread($fp, $ThisFileInfo['filesize'] - $ThisFileInfo['lyrics3']['tag_offset_end']);
|
||||||
|
}
|
||||||
|
|
||||||
|
ftruncate($fp, $ThisFileInfo['lyrics3']['tag_offset_start']);
|
||||||
|
|
||||||
|
if (!empty($DataAfterLyrics3)) {
|
||||||
|
fseek($fp, $ThisFileInfo['lyrics3']['tag_offset_start'], SEEK_SET);
|
||||||
|
fwrite($fp, $DataAfterLyrics3, strlen($DataAfterLyrics3));
|
||||||
|
}
|
||||||
|
|
||||||
|
flock($fp, LOCK_UN);
|
||||||
|
fclose($fp);
|
||||||
|
ignore_user_abort($oldignoreuserabort);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'Cannot fopen('.$this->filename.', "a+b")';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no Lyrics3 present
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
163
app/library/getid3/write.metaflac.php
Normal file
163
app/library/getid3/write.metaflac.php
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// write.metaflac.php //
|
||||||
|
// module for writing metaflac tags //
|
||||||
|
// dependencies: /helperapps/metaflac.exe //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_write_metaflac
|
||||||
|
{
|
||||||
|
|
||||||
|
var $filename;
|
||||||
|
var $tag_data;
|
||||||
|
var $warnings = array(); // any non-critical errors will be stored here
|
||||||
|
var $errors = array(); // any critical errors will be stored here
|
||||||
|
|
||||||
|
function getid3_write_metaflac() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function WriteMetaFLAC() {
|
||||||
|
|
||||||
|
if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
|
||||||
|
$this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call metaflac, tags not written';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create file with new comments
|
||||||
|
$tempcommentsfilename = tempnam(GETID3_TEMP_DIR, 'getID3');
|
||||||
|
if (is_writable($tempcommentsfilename) && is_file($tempcommentsfilename) && ($fpcomments = fopen($tempcommentsfilename, 'wb'))) {
|
||||||
|
foreach ($this->tag_data as $key => $value) {
|
||||||
|
foreach ($value as $commentdata) {
|
||||||
|
fwrite($fpcomments, $this->CleanmetaflacName($key).'='.$commentdata."\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose($fpcomments);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'failed to open temporary tags file, tags not written - fopen("'.$tempcommentsfilename.'", "wb")';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldignoreuserabort = ignore_user_abort(true);
|
||||||
|
if (GETID3_OS_ISWINDOWS) {
|
||||||
|
|
||||||
|
if (file_exists(GETID3_HELPERAPPSDIR.'metaflac.exe')) {
|
||||||
|
//$commandline = '"'.GETID3_HELPERAPPSDIR.'metaflac.exe" --no-utf8-convert --remove-all-tags --import-tags-from="'.$tempcommentsfilename.'" "'.str_replace('/', '\\', $this->filename).'"';
|
||||||
|
// metaflac works fine if you copy-paste the above commandline into a command prompt,
|
||||||
|
// but refuses to work with `backtick` if there are "doublequotes" present around BOTH
|
||||||
|
// the metaflac pathname and the target filename. For whatever reason...??
|
||||||
|
// The solution is simply ensure that the metaflac pathname has no spaces,
|
||||||
|
// and therefore does not need to be quoted
|
||||||
|
|
||||||
|
// On top of that, if error messages are not always captured properly under Windows
|
||||||
|
// To at least see if there was a problem, compare file modification timestamps before and after writing
|
||||||
|
clearstatcache();
|
||||||
|
$timestampbeforewriting = filemtime($this->filename);
|
||||||
|
|
||||||
|
$commandline = GETID3_HELPERAPPSDIR.'metaflac.exe --no-utf8-convert --remove-all-tags --import-tags-from='.escapeshellarg($tempcommentsfilename).' '.escapeshellarg($this->filename).' 2>&1';
|
||||||
|
$metaflacError = `$commandline`;
|
||||||
|
|
||||||
|
if (empty($metaflacError)) {
|
||||||
|
clearstatcache();
|
||||||
|
if ($timestampbeforewriting == filemtime($this->filename)) {
|
||||||
|
$metaflacError = 'File modification timestamp has not changed - it looks like the tags were not written';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$metaflacError = 'metaflac.exe not found in '.GETID3_HELPERAPPSDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// It's simpler on *nix
|
||||||
|
$commandline = 'metaflac --no-utf8-convert --remove-all-tags --import-tags-from='.escapeshellarg($tempcommentsfilename).' '.escapeshellarg($this->filename).' 2>&1';
|
||||||
|
$metaflacError = `$commandline`;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove temporary comments file
|
||||||
|
unlink($tempcommentsfilename);
|
||||||
|
ignore_user_abort($oldignoreuserabort);
|
||||||
|
|
||||||
|
if (!empty($metaflacError)) {
|
||||||
|
|
||||||
|
$this->errors[] = 'System call to metaflac failed with this message returned: '."\n\n".$metaflacError;
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function DeleteMetaFLAC() {
|
||||||
|
|
||||||
|
if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
|
||||||
|
$this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call metaflac, tags not deleted';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldignoreuserabort = ignore_user_abort(true);
|
||||||
|
if (GETID3_OS_ISWINDOWS) {
|
||||||
|
|
||||||
|
if (file_exists(GETID3_HELPERAPPSDIR.'metaflac.exe')) {
|
||||||
|
// To at least see if there was a problem, compare file modification timestamps before and after writing
|
||||||
|
clearstatcache();
|
||||||
|
$timestampbeforewriting = filemtime($this->filename);
|
||||||
|
|
||||||
|
$commandline = GETID3_HELPERAPPSDIR.'metaflac.exe --remove-all-tags "'.$this->filename.'" 2>&1';
|
||||||
|
$metaflacError = `$commandline`;
|
||||||
|
|
||||||
|
if (empty($metaflacError)) {
|
||||||
|
clearstatcache();
|
||||||
|
if ($timestampbeforewriting == filemtime($this->filename)) {
|
||||||
|
$metaflacError = 'File modification timestamp has not changed - it looks like the tags were not deleted';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$metaflacError = 'metaflac.exe not found in '.GETID3_HELPERAPPSDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// It's simpler on *nix
|
||||||
|
$commandline = 'metaflac --remove-all-tags "'.$this->filename.'" 2>&1';
|
||||||
|
$metaflacError = `$commandline`;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore_user_abort($oldignoreuserabort);
|
||||||
|
|
||||||
|
if (!empty($metaflacError)) {
|
||||||
|
$this->errors[] = 'System call to metaflac failed with this message returned: '."\n\n".$metaflacError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function CleanmetaflacName($originalcommentname) {
|
||||||
|
// A case-insensitive field name that may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded.
|
||||||
|
// ASCII 0x41 through 0x5A inclusive (A-Z) is to be considered equivalent to ASCII 0x61 through
|
||||||
|
// 0x7A inclusive (a-z).
|
||||||
|
|
||||||
|
// replace invalid chars with a space, return uppercase text
|
||||||
|
// Thanks Chris Bolt <chris-getid3Øbolt*cx> for improving this function
|
||||||
|
// note: *reg_replace() replaces nulls with empty string (not space)
|
||||||
|
return strtoupper(preg_replace('#[^ -<>-}]#', ' ', str_replace("\x00", ' ', $originalcommentname)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
615
app/library/getid3/write.php
Normal file
615
app/library/getid3/write.php
Normal file
|
@ -0,0 +1,615 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// //
|
||||||
|
// write.php //
|
||||||
|
// module for writing tags (APEv2, ID3v1, ID3v2) //
|
||||||
|
// dependencies: getid3.lib.php //
|
||||||
|
// write.apetag.php (optional) //
|
||||||
|
// write.id3v1.php (optional) //
|
||||||
|
// write.id3v2.php (optional) //
|
||||||
|
// write.vorbiscomment.php (optional) //
|
||||||
|
// write.metaflac.php (optional) //
|
||||||
|
// write.lyrics3.php (optional) //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
if (!defined('GETID3_INCLUDEPATH')) {
|
||||||
|
throw new Exception('getid3.php MUST be included before calling getid3_writetags');
|
||||||
|
}
|
||||||
|
if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
|
||||||
|
throw new Exception('write.php depends on getid3.lib.php, which is missing.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// NOTES:
|
||||||
|
//
|
||||||
|
// You should pass data here with standard field names as follows:
|
||||||
|
// * TITLE
|
||||||
|
// * ARTIST
|
||||||
|
// * ALBUM
|
||||||
|
// * TRACKNUMBER
|
||||||
|
// * COMMENT
|
||||||
|
// * GENRE
|
||||||
|
// * YEAR
|
||||||
|
// * ATTACHED_PICTURE (ID3v2 only)
|
||||||
|
//
|
||||||
|
// http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html
|
||||||
|
// The APEv2 Tag Items Keys definition says "TRACK" is correct but foobar2000 uses "TRACKNUMBER" instead
|
||||||
|
// Pass data here as "TRACKNUMBER" for compatability with all formats
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_writetags
|
||||||
|
{
|
||||||
|
// public
|
||||||
|
var $filename; // absolute filename of file to write tags to
|
||||||
|
var $tagformats = array(); // array of tag formats to write ('id3v1', 'id3v2.2', 'id2v2.3', 'id3v2.4', 'ape', 'vorbiscomment', 'metaflac', 'real')
|
||||||
|
var $tag_data = array(array()); // 2-dimensional array of tag data (ex: $data['ARTIST'][0] = 'Elvis')
|
||||||
|
var $tag_encoding = 'ISO-8859-1'; // text encoding used for tag data ('ISO-8859-1', 'UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE', )
|
||||||
|
var $overwrite_tags = true; // if true will erase existing tag data and write only passed data; if false will merge passed data with existing tag data
|
||||||
|
var $remove_other_tags = false; // if true will erase remove all existing tags and only write those passed in $tagformats; if false will ignore any tags not mentioned in $tagformats
|
||||||
|
|
||||||
|
var $id3v2_tag_language = 'eng'; // ISO-639-2 3-character language code needed for some ID3v2 frames (http://www.id3.org/iso639-2.html)
|
||||||
|
var $id3v2_paddedlength = 4096; // minimum length of ID3v2 tags (will be padded to this length if tag data is shorter)
|
||||||
|
|
||||||
|
var $warnings = array(); // any non-critical errors will be stored here
|
||||||
|
var $errors = array(); // any critical errors will be stored here
|
||||||
|
|
||||||
|
// private
|
||||||
|
var $ThisFileInfo; // analysis of file before writing
|
||||||
|
|
||||||
|
function getid3_writetags() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function WriteTags() {
|
||||||
|
|
||||||
|
if (empty($this->filename)) {
|
||||||
|
$this->errors[] = 'filename is undefined in getid3_writetags';
|
||||||
|
return false;
|
||||||
|
} elseif (!file_exists($this->filename)) {
|
||||||
|
$this->errors[] = 'filename set to non-existant file "'.$this->filename.'" in getid3_writetags';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($this->tagformats)) {
|
||||||
|
$this->errors[] = 'tagformats must be an array in getid3_writetags';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$TagFormatsToRemove = array();
|
||||||
|
if (filesize($this->filename) == 0) {
|
||||||
|
|
||||||
|
// empty file special case - allow any tag format, don't check existing format
|
||||||
|
// could be useful if you want to generate tag data for a non-existant file
|
||||||
|
$this->ThisFileInfo = array('fileformat'=>'');
|
||||||
|
$AllowedTagFormats = array('id3v1', 'id3v2.2', 'id3v2.3', 'id3v2.4', 'ape', 'lyrics3');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$getID3 = new getID3;
|
||||||
|
$getID3->encoding = $this->tag_encoding;
|
||||||
|
$this->ThisFileInfo = $getID3->analyze($this->filename);
|
||||||
|
|
||||||
|
// check for what file types are allowed on this fileformat
|
||||||
|
switch (isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : '') {
|
||||||
|
case 'mp3':
|
||||||
|
case 'mp2':
|
||||||
|
case 'mp1':
|
||||||
|
case 'riff': // maybe not officially, but people do it anyway
|
||||||
|
$AllowedTagFormats = array('id3v1', 'id3v2.2', 'id3v2.3', 'id3v2.4', 'ape', 'lyrics3');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'mpc':
|
||||||
|
$AllowedTagFormats = array('ape');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'flac':
|
||||||
|
$AllowedTagFormats = array('metaflac');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'real':
|
||||||
|
$AllowedTagFormats = array('real');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ogg':
|
||||||
|
switch (isset($this->ThisFileInfo['audio']['dataformat']) ? $this->ThisFileInfo['audio']['dataformat'] : '') {
|
||||||
|
case 'flac':
|
||||||
|
//$AllowedTagFormats = array('metaflac');
|
||||||
|
$this->errors[] = 'metaflac is not (yet) compatible with OggFLAC files';
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case 'vorbis':
|
||||||
|
$AllowedTagFormats = array('vorbiscomment');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->errors[] = 'metaflac is not (yet) compatible with Ogg files other than OggVorbis';
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$AllowedTagFormats = array();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
foreach ($this->tagformats as $requested_tag_format) {
|
||||||
|
if (!in_array($requested_tag_format, $AllowedTagFormats)) {
|
||||||
|
$errormessage = 'Tag format "'.$requested_tag_format.'" is not allowed on "'.(isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : '');
|
||||||
|
$errormessage .= (isset($this->ThisFileInfo['audio']['dataformat']) ? '.'.$this->ThisFileInfo['audio']['dataformat'] : '');
|
||||||
|
$errormessage .= '" files';
|
||||||
|
$this->errors[] = $errormessage;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of other tag formats, removed if requested
|
||||||
|
if ($this->remove_other_tags) {
|
||||||
|
foreach ($AllowedTagFormats as $AllowedTagFormat) {
|
||||||
|
switch ($AllowedTagFormat) {
|
||||||
|
case 'id3v2.2':
|
||||||
|
case 'id3v2.3':
|
||||||
|
case 'id3v2.4':
|
||||||
|
if (!in_array('id3v2', $TagFormatsToRemove) && !in_array('id3v2.2', $this->tagformats) && !in_array('id3v2.3', $this->tagformats) && !in_array('id3v2.4', $this->tagformats)) {
|
||||||
|
$TagFormatsToRemove[] = 'id3v2';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (!in_array($AllowedTagFormat, $this->tagformats)) {
|
||||||
|
$TagFormatsToRemove[] = $AllowedTagFormat;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$WritingFilesToInclude = array_merge($this->tagformats, $TagFormatsToRemove);
|
||||||
|
|
||||||
|
// Check for required include files and include them
|
||||||
|
foreach ($WritingFilesToInclude as $tagformat) {
|
||||||
|
switch ($tagformat) {
|
||||||
|
case 'ape':
|
||||||
|
$GETID3_ERRORARRAY = &$this->errors;
|
||||||
|
if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.apetag.php', __FILE__, false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'id3v1':
|
||||||
|
case 'lyrics3':
|
||||||
|
case 'vorbiscomment':
|
||||||
|
case 'metaflac':
|
||||||
|
case 'real':
|
||||||
|
$GETID3_ERRORARRAY = &$this->errors;
|
||||||
|
if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.'.$tagformat.'.php', __FILE__, false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'id3v2.2':
|
||||||
|
case 'id3v2.3':
|
||||||
|
case 'id3v2.4':
|
||||||
|
case 'id3v2':
|
||||||
|
$GETID3_ERRORARRAY = &$this->errors;
|
||||||
|
if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.id3v2.php', __FILE__, false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$this->errors[] = 'unknown tag format "'.$tagformat.'" in $tagformats in WriteTags()';
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation of supplied data
|
||||||
|
if (!is_array($this->tag_data)) {
|
||||||
|
$this->errors[] = '$this->tag_data is not an array in WriteTags()';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// convert supplied data array keys to upper case, if they're not already
|
||||||
|
foreach ($this->tag_data as $tag_key => $tag_array) {
|
||||||
|
if (strtoupper($tag_key) !== $tag_key) {
|
||||||
|
$this->tag_data[strtoupper($tag_key)] = $this->tag_data[$tag_key];
|
||||||
|
unset($this->tag_data[$tag_key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// convert source data array keys to upper case, if they're not already
|
||||||
|
if (!empty($this->ThisFileInfo['tags'])) {
|
||||||
|
foreach ($this->ThisFileInfo['tags'] as $tag_format => $tag_data_array) {
|
||||||
|
foreach ($tag_data_array as $tag_key => $tag_array) {
|
||||||
|
if (strtoupper($tag_key) !== $tag_key) {
|
||||||
|
$this->ThisFileInfo['tags'][$tag_format][strtoupper($tag_key)] = $this->ThisFileInfo['tags'][$tag_format][$tag_key];
|
||||||
|
unset($this->ThisFileInfo['tags'][$tag_format][$tag_key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert "TRACK" to "TRACKNUMBER" (if needed) for compatability with all formats
|
||||||
|
if (isset($this->tag_data['TRACK']) && !isset($this->tag_data['TRACKNUMBER'])) {
|
||||||
|
$this->tag_data['TRACKNUMBER'] = $this->tag_data['TRACK'];
|
||||||
|
unset($this->tag_data['TRACK']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all other tag formats, if requested
|
||||||
|
if ($this->remove_other_tags) {
|
||||||
|
$this->DeleteTags($TagFormatsToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data for each tag format
|
||||||
|
foreach ($this->tagformats as $tagformat) {
|
||||||
|
$success = false; // overridden if tag writing is successful
|
||||||
|
switch ($tagformat) {
|
||||||
|
case 'ape':
|
||||||
|
$ape_writer = new getid3_write_apetag;
|
||||||
|
if (($ape_writer->tag_data = $this->FormatDataForAPE()) !== false) {
|
||||||
|
$ape_writer->filename = $this->filename;
|
||||||
|
if (($success = $ape_writer->WriteAPEtag()) === false) {
|
||||||
|
$this->errors[] = 'WriteAPEtag() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $ape_writer->errors)))).'</li></ul></pre>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'FormatDataForAPE() failed';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'id3v1':
|
||||||
|
$id3v1_writer = new getid3_write_id3v1;
|
||||||
|
if (($id3v1_writer->tag_data = $this->FormatDataForID3v1()) !== false) {
|
||||||
|
$id3v1_writer->filename = $this->filename;
|
||||||
|
if (($success = $id3v1_writer->WriteID3v1()) === false) {
|
||||||
|
$this->errors[] = 'WriteID3v1() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $id3v1_writer->errors)))).'</li></ul></pre>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'FormatDataForID3v1() failed';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'id3v2.2':
|
||||||
|
case 'id3v2.3':
|
||||||
|
case 'id3v2.4':
|
||||||
|
$id3v2_writer = new getid3_write_id3v2;
|
||||||
|
$id3v2_writer->majorversion = intval(substr($tagformat, -1));
|
||||||
|
$id3v2_writer->paddedlength = $this->id3v2_paddedlength;
|
||||||
|
if (($id3v2_writer->tag_data = $this->FormatDataForID3v2($id3v2_writer->majorversion)) !== false) {
|
||||||
|
$id3v2_writer->filename = $this->filename;
|
||||||
|
if (($success = $id3v2_writer->WriteID3v2()) === false) {
|
||||||
|
$this->errors[] = 'WriteID3v2() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $id3v2_writer->errors)))).'</li></ul></pre>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'FormatDataForID3v2() failed';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'vorbiscomment':
|
||||||
|
$vorbiscomment_writer = new getid3_write_vorbiscomment;
|
||||||
|
if (($vorbiscomment_writer->tag_data = $this->FormatDataForVorbisComment()) !== false) {
|
||||||
|
$vorbiscomment_writer->filename = $this->filename;
|
||||||
|
if (($success = $vorbiscomment_writer->WriteVorbisComment()) === false) {
|
||||||
|
$this->errors[] = 'WriteVorbisComment() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $vorbiscomment_writer->errors)))).'</li></ul></pre>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'FormatDataForVorbisComment() failed';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'metaflac':
|
||||||
|
$metaflac_writer = new getid3_write_metaflac;
|
||||||
|
if (($metaflac_writer->tag_data = $this->FormatDataForMetaFLAC()) !== false) {
|
||||||
|
$metaflac_writer->filename = $this->filename;
|
||||||
|
if (($success = $metaflac_writer->WriteMetaFLAC()) === false) {
|
||||||
|
$this->errors[] = 'WriteMetaFLAC() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $metaflac_writer->errors)))).'</li></ul></pre>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'FormatDataForMetaFLAC() failed';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'real':
|
||||||
|
$real_writer = new getid3_write_real;
|
||||||
|
if (($real_writer->tag_data = $this->FormatDataForReal()) !== false) {
|
||||||
|
$real_writer->filename = $this->filename;
|
||||||
|
if (($success = $real_writer->WriteReal()) === false) {
|
||||||
|
$this->errors[] = 'WriteReal() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $real_writer->errors)))).'</li></ul></pre>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'FormatDataForReal() failed';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$this->errors[] = 'Invalid tag format to write: "'.$tagformat.'"';
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!$success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function DeleteTags($TagFormatsToDelete) {
|
||||||
|
foreach ($TagFormatsToDelete as $DeleteTagFormat) {
|
||||||
|
$success = false; // overridden if tag deletion is successful
|
||||||
|
switch ($DeleteTagFormat) {
|
||||||
|
case 'id3v1':
|
||||||
|
$id3v1_writer = new getid3_write_id3v1;
|
||||||
|
$id3v1_writer->filename = $this->filename;
|
||||||
|
if (($success = $id3v1_writer->RemoveID3v1()) === false) {
|
||||||
|
$this->errors[] = 'RemoveID3v1() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v1_writer->errors)).'</LI></UL></PRE>';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'id3v2':
|
||||||
|
$id3v2_writer = new getid3_write_id3v2;
|
||||||
|
$id3v2_writer->filename = $this->filename;
|
||||||
|
if (($success = $id3v2_writer->RemoveID3v2()) === false) {
|
||||||
|
$this->errors[] = 'RemoveID3v2() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v2_writer->errors)).'</LI></UL></PRE>';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ape':
|
||||||
|
$ape_writer = new getid3_write_apetag;
|
||||||
|
$ape_writer->filename = $this->filename;
|
||||||
|
if (($success = $ape_writer->DeleteAPEtag()) === false) {
|
||||||
|
$this->errors[] = 'DeleteAPEtag() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $ape_writer->errors)).'</LI></UL></PRE>';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'vorbiscomment':
|
||||||
|
$vorbiscomment_writer = new getid3_write_vorbiscomment;
|
||||||
|
$vorbiscomment_writer->filename = $this->filename;
|
||||||
|
if (($success = $vorbiscomment_writer->DeleteVorbisComment()) === false) {
|
||||||
|
$this->errors[] = 'DeleteVorbisComment() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $vorbiscomment_writer->errors)).'</LI></UL></PRE>';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'metaflac':
|
||||||
|
$metaflac_writer = new getid3_write_metaflac;
|
||||||
|
$metaflac_writer->filename = $this->filename;
|
||||||
|
if (($success = $metaflac_writer->DeleteMetaFLAC()) === false) {
|
||||||
|
$this->errors[] = 'DeleteMetaFLAC() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $metaflac_writer->errors)).'</LI></UL></PRE>';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'lyrics3':
|
||||||
|
$lyrics3_writer = new getid3_write_lyrics3;
|
||||||
|
$lyrics3_writer->filename = $this->filename;
|
||||||
|
if (($success = $lyrics3_writer->DeleteLyrics3()) === false) {
|
||||||
|
$this->errors[] = 'DeleteLyrics3() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $lyrics3_writer->errors)).'</LI></UL></PRE>';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'real':
|
||||||
|
$real_writer = new getid3_write_real;
|
||||||
|
$real_writer->filename = $this->filename;
|
||||||
|
if (($success = $real_writer->RemoveReal()) === false) {
|
||||||
|
$this->errors[] = 'RemoveReal() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $real_writer->errors)).'</LI></UL></PRE>';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$this->errors[] = 'Invalid tag format to delete: "'.$tagformat.'"';
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!$success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function MergeExistingTagData($TagFormat, &$tag_data) {
|
||||||
|
// Merge supplied data with existing data, if requested
|
||||||
|
if ($this->overwrite_tags) {
|
||||||
|
// do nothing - ignore previous data
|
||||||
|
} else {
|
||||||
|
throw new Exception('$this->overwrite_tags=false is known to be buggy in this version of getID3. Will be fixed in the near future, check www.getid3.org for a newer version.');
|
||||||
|
if (!isset($this->ThisFileInfo['tags'][$TagFormat])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$tag_data = array_merge_recursive($tag_data, $this->ThisFileInfo['tags'][$TagFormat]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FormatDataForAPE() {
|
||||||
|
$ape_tag_data = array();
|
||||||
|
foreach ($this->tag_data as $tag_key => $valuearray) {
|
||||||
|
switch ($tag_key) {
|
||||||
|
case 'ATTACHED_PICTURE':
|
||||||
|
// ATTACHED_PICTURE is ID3v2 only - ignore
|
||||||
|
$this->warnings[] = '$data['.$tag_key.'] is assumed to be ID3v2 APIC data - NOT written to APE tag';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
foreach ($valuearray as $key => $value) {
|
||||||
|
if (is_string($value) || is_numeric($value)) {
|
||||||
|
$ape_tag_data[$tag_key][$key] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value);
|
||||||
|
} else {
|
||||||
|
$this->warnings[] = '$data['.$tag_key.']['.$key.'] is not a string value - all of $data['.$tag_key.'] NOT written to APE tag';
|
||||||
|
unset($ape_tag_data[$tag_key]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->MergeExistingTagData('ape', $ape_tag_data);
|
||||||
|
return $ape_tag_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function FormatDataForID3v1() {
|
||||||
|
$tag_data_id3v1['genreid'] = 255;
|
||||||
|
if (!empty($this->tag_data['GENRE'])) {
|
||||||
|
foreach ($this->tag_data['GENRE'] as $key => $value) {
|
||||||
|
if (getid3_id3v1::LookupGenreID($value) !== false) {
|
||||||
|
$tag_data_id3v1['genreid'] = getid3_id3v1::LookupGenreID($value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$tag_data_id3v1['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TITLE'] ) ? $this->tag_data['TITLE'] : array())));
|
||||||
|
$tag_data_id3v1['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ARTIST'] ) ? $this->tag_data['ARTIST'] : array())));
|
||||||
|
$tag_data_id3v1['album'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ALBUM'] ) ? $this->tag_data['ALBUM'] : array())));
|
||||||
|
$tag_data_id3v1['year'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['YEAR'] ) ? $this->tag_data['YEAR'] : array())));
|
||||||
|
$tag_data_id3v1['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COMMENT'] ) ? $this->tag_data['COMMENT'] : array())));
|
||||||
|
$tag_data_id3v1['track'] = intval(getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TRACKNUMBER']) ? $this->tag_data['TRACKNUMBER'] : array()))));
|
||||||
|
if ($tag_data_id3v1['track'] <= 0) {
|
||||||
|
$tag_data_id3v1['track'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->MergeExistingTagData('id3v1', $tag_data_id3v1);
|
||||||
|
return $tag_data_id3v1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FormatDataForID3v2($id3v2_majorversion) {
|
||||||
|
$tag_data_id3v2 = array();
|
||||||
|
|
||||||
|
$ID3v2_text_encoding_lookup[2] = array('ISO-8859-1'=>0, 'UTF-16'=>1);
|
||||||
|
$ID3v2_text_encoding_lookup[3] = array('ISO-8859-1'=>0, 'UTF-16'=>1);
|
||||||
|
$ID3v2_text_encoding_lookup[4] = array('ISO-8859-1'=>0, 'UTF-16'=>1, 'UTF-16BE'=>2, 'UTF-8'=>3);
|
||||||
|
foreach ($this->tag_data as $tag_key => $valuearray) {
|
||||||
|
$ID3v2_framename = getid3_write_id3v2::ID3v2ShortFrameNameLookup($id3v2_majorversion, $tag_key);
|
||||||
|
switch ($ID3v2_framename) {
|
||||||
|
case 'APIC':
|
||||||
|
foreach ($valuearray as $key => $apic_data_array) {
|
||||||
|
if (isset($apic_data_array['data']) &&
|
||||||
|
isset($apic_data_array['picturetypeid']) &&
|
||||||
|
isset($apic_data_array['description']) &&
|
||||||
|
isset($apic_data_array['mime'])) {
|
||||||
|
$tag_data_id3v2['APIC'][] = $apic_data_array;
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'ID3v2 APIC data is not properly structured';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '':
|
||||||
|
$this->errors[] = 'ID3v2: Skipping "'.$tag_key.'" because cannot match it to a known ID3v2 frame type';
|
||||||
|
// some other data type, don't know how to handle it, ignore it
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// most other (text) frames can be copied over as-is
|
||||||
|
foreach ($valuearray as $key => $value) {
|
||||||
|
if (isset($ID3v2_text_encoding_lookup[$id3v2_majorversion][$this->tag_encoding])) {
|
||||||
|
// source encoding is valid in ID3v2 - use it with no conversion
|
||||||
|
$tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = $ID3v2_text_encoding_lookup[$id3v2_majorversion][$this->tag_encoding];
|
||||||
|
$tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
|
||||||
|
} else {
|
||||||
|
// source encoding is NOT valid in ID3v2 - convert it to an ID3v2-valid encoding first
|
||||||
|
if ($id3v2_majorversion < 4) {
|
||||||
|
// convert data from other encoding to UTF-16 (with BOM)
|
||||||
|
// note: some software, notably Windows Media Player and iTunes are broken and treat files tagged with UTF-16BE (with BOM) as corrupt
|
||||||
|
// therefore we force data to UTF-16LE and manually prepend the BOM
|
||||||
|
$ID3v2_tag_data_converted = false;
|
||||||
|
if (!$ID3v2_tag_data_converted && ($this->tag_encoding == 'ISO-8859-1')) {
|
||||||
|
// great, leave data as-is for minimum compatability problems
|
||||||
|
$tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0;
|
||||||
|
$tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
|
||||||
|
$ID3v2_tag_data_converted = true;
|
||||||
|
}
|
||||||
|
if (!$ID3v2_tag_data_converted && ($this->tag_encoding == 'UTF-8')) {
|
||||||
|
do {
|
||||||
|
// if UTF-8 string does not include any characters above chr(127) then it is identical to ISO-8859-1
|
||||||
|
for ($i = 0; $i < strlen($value); $i++) {
|
||||||
|
if (ord($value{$i}) > 127) {
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0;
|
||||||
|
$tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value;
|
||||||
|
$ID3v2_tag_data_converted = true;
|
||||||
|
} while (false);
|
||||||
|
}
|
||||||
|
if (!$ID3v2_tag_data_converted) {
|
||||||
|
$tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 1;
|
||||||
|
//$tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-16', $value); // output is UTF-16LE+BOM or UTF-16BE+BOM depending on system architecture
|
||||||
|
$tag_data_id3v2[$ID3v2_framename][$key]['data'] = "\xFF\xFE".getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-16LE', $value); // force LittleEndian order version of UTF-16
|
||||||
|
$ID3v2_tag_data_converted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// convert data from other encoding to UTF-8
|
||||||
|
$tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 3;
|
||||||
|
$tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These values are not needed for all frame types, but if they're not used no matter
|
||||||
|
$tag_data_id3v2[$ID3v2_framename][$key]['description'] = '';
|
||||||
|
$tag_data_id3v2[$ID3v2_framename][$key]['language'] = $this->id3v2_tag_language;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->MergeExistingTagData('id3v2', $tag_data_id3v2);
|
||||||
|
return $tag_data_id3v2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FormatDataForVorbisComment() {
|
||||||
|
$tag_data_vorbiscomment = $this->tag_data;
|
||||||
|
|
||||||
|
// check for multi-line comment values - split out to multiple comments if neccesary
|
||||||
|
// and convert data to UTF-8 strings
|
||||||
|
foreach ($tag_data_vorbiscomment as $tag_key => $valuearray) {
|
||||||
|
foreach ($valuearray as $key => $value) {
|
||||||
|
str_replace("\r", "\n", $value);
|
||||||
|
if (strstr($value, "\n")) {
|
||||||
|
unset($tag_data_vorbiscomment[$tag_key][$key]);
|
||||||
|
$multilineexploded = explode("\n", $value);
|
||||||
|
foreach ($multilineexploded as $newcomment) {
|
||||||
|
if (strlen(trim($newcomment)) > 0) {
|
||||||
|
$tag_data_vorbiscomment[$tag_key][] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $newcomment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif (is_string($value) || is_numeric($value)) {
|
||||||
|
$tag_data_vorbiscomment[$tag_key][$key] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value);
|
||||||
|
} else {
|
||||||
|
$this->warnings[] = '$data['.$tag_key.']['.$key.'] is not a string value - all of $data['.$tag_key.'] NOT written to VorbisComment tag';
|
||||||
|
unset($tag_data_vorbiscomment[$tag_key]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->MergeExistingTagData('vorbiscomment', $tag_data_vorbiscomment);
|
||||||
|
return $tag_data_vorbiscomment;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FormatDataForMetaFLAC() {
|
||||||
|
// FLAC & OggFLAC use VorbisComments same as OggVorbis
|
||||||
|
// but require metaflac to do the writing rather than vorbiscomment
|
||||||
|
return $this->FormatDataForVorbisComment();
|
||||||
|
}
|
||||||
|
|
||||||
|
function FormatDataForReal() {
|
||||||
|
$tag_data_real['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TITLE'] ) ? $this->tag_data['TITLE'] : array())));
|
||||||
|
$tag_data_real['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ARTIST'] ) ? $this->tag_data['ARTIST'] : array())));
|
||||||
|
$tag_data_real['copyright'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COPYRIGHT']) ? $this->tag_data['COPYRIGHT'] : array())));
|
||||||
|
$tag_data_real['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COMMENT'] ) ? $this->tag_data['COMMENT'] : array())));
|
||||||
|
|
||||||
|
$this->MergeExistingTagData('real', $tag_data_real);
|
||||||
|
return $tag_data_real;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
275
app/library/getid3/write.real.php
Normal file
275
app/library/getid3/write.real.php
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// write.real.php //
|
||||||
|
// module for writing RealAudio/RealVideo tags //
|
||||||
|
// dependencies: module.tag.real.php //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
class getid3_write_real
|
||||||
|
{
|
||||||
|
var $filename;
|
||||||
|
var $tag_data = array();
|
||||||
|
var $fread_buffer_size = 32768; // read buffer size in bytes
|
||||||
|
var $warnings = array(); // any non-critical errors will be stored here
|
||||||
|
var $errors = array(); // any critical errors will be stored here
|
||||||
|
var $paddedlength = 512; // minimum length of CONT tag in bytes
|
||||||
|
|
||||||
|
function getid3_write_real() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function WriteReal() {
|
||||||
|
// File MUST be writeable - CHMOD(646) at least
|
||||||
|
if (is_writeable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'r+b'))) {
|
||||||
|
|
||||||
|
// Initialize getID3 engine
|
||||||
|
$getID3 = new getID3;
|
||||||
|
$OldThisFileInfo = $getID3->analyze($this->filename);
|
||||||
|
if (empty($OldThisFileInfo['real']['chunks']) && !empty($OldThisFileInfo['real']['old_ra_header'])) {
|
||||||
|
$this->errors[] = 'Cannot write Real tags on old-style file format';
|
||||||
|
fclose($fp_source);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($OldThisFileInfo['real']['chunks'])) {
|
||||||
|
$this->errors[] = 'Cannot write Real tags because cannot find DATA chunk in file';
|
||||||
|
fclose($fp_source);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) {
|
||||||
|
$oldChunkInfo[$chunkarray['name']] = $chunkarray;
|
||||||
|
}
|
||||||
|
if (!empty($oldChunkInfo['CONT']['length'])) {
|
||||||
|
$this->paddedlength = max($oldChunkInfo['CONT']['length'], $this->paddedlength);
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_CONT_tag_data = $this->GenerateCONTchunk();
|
||||||
|
$new_PROP_tag_data = $this->GeneratePROPchunk($OldThisFileInfo['real']['chunks'], $new_CONT_tag_data);
|
||||||
|
$new__RMF_tag_data = $this->GenerateRMFchunk($OldThisFileInfo['real']['chunks']);
|
||||||
|
|
||||||
|
if (isset($oldChunkInfo['.RMF']['length']) && ($oldChunkInfo['.RMF']['length'] == strlen($new__RMF_tag_data))) {
|
||||||
|
fseek($fp_source, $oldChunkInfo['.RMF']['offset'], SEEK_SET);
|
||||||
|
fwrite($fp_source, $new__RMF_tag_data);
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'new .RMF tag ('.strlen($new__RMF_tag_data).' bytes) different length than old .RMF tag ('.$oldChunkInfo['.RMF']['length'].' bytes)';
|
||||||
|
fclose($fp_source);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($oldChunkInfo['PROP']['length']) && ($oldChunkInfo['PROP']['length'] == strlen($new_PROP_tag_data))) {
|
||||||
|
fseek($fp_source, $oldChunkInfo['PROP']['offset'], SEEK_SET);
|
||||||
|
fwrite($fp_source, $new_PROP_tag_data);
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'new PROP tag ('.strlen($new_PROP_tag_data).' bytes) different length than old PROP tag ('.$oldChunkInfo['PROP']['length'].' bytes)';
|
||||||
|
fclose($fp_source);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($oldChunkInfo['CONT']['length']) && ($oldChunkInfo['CONT']['length'] == strlen($new_CONT_tag_data))) {
|
||||||
|
|
||||||
|
// new data length is same as old data length - just overwrite
|
||||||
|
fseek($fp_source, $oldChunkInfo['CONT']['offset'], SEEK_SET);
|
||||||
|
fwrite($fp_source, $new_CONT_tag_data);
|
||||||
|
fclose($fp_source);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (empty($oldChunkInfo['CONT'])) {
|
||||||
|
// no existing CONT chunk
|
||||||
|
$BeforeOffset = $oldChunkInfo['DATA']['offset'];
|
||||||
|
$AfterOffset = $oldChunkInfo['DATA']['offset'];
|
||||||
|
} else {
|
||||||
|
// new data is longer than old data
|
||||||
|
$BeforeOffset = $oldChunkInfo['CONT']['offset'];
|
||||||
|
$AfterOffset = $oldChunkInfo['CONT']['offset'] + $oldChunkInfo['CONT']['length'];
|
||||||
|
}
|
||||||
|
if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) {
|
||||||
|
if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) {
|
||||||
|
|
||||||
|
rewind($fp_source);
|
||||||
|
fwrite($fp_temp, fread($fp_source, $BeforeOffset));
|
||||||
|
fwrite($fp_temp, $new_CONT_tag_data);
|
||||||
|
fseek($fp_source, $AfterOffset, SEEK_SET);
|
||||||
|
while ($buffer = fread($fp_source, $this->fread_buffer_size)) {
|
||||||
|
fwrite($fp_temp, $buffer, strlen($buffer));
|
||||||
|
}
|
||||||
|
fclose($fp_temp);
|
||||||
|
|
||||||
|
if (copy($tempfilename, $this->filename)) {
|
||||||
|
unlink($tempfilename);
|
||||||
|
fclose($fp_source);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
unlink($tempfilename);
|
||||||
|
$this->errors[] = 'FAILED: copy('.$tempfilename.', '.$this->filename.')';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose($fp_source);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
$this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GenerateRMFchunk(&$chunks) {
|
||||||
|
$oldCONTexists = false;
|
||||||
|
foreach ($chunks as $key => $chunk) {
|
||||||
|
$chunkNameKeys[$chunk['name']] = $key;
|
||||||
|
if ($chunk['name'] == 'CONT') {
|
||||||
|
$oldCONTexists = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$newHeadersCount = $chunks[$chunkNameKeys['.RMF']]['headers_count'] + ($oldCONTexists ? 0 : 1);
|
||||||
|
|
||||||
|
$RMFchunk = "\x00\x00"; // object version
|
||||||
|
$RMFchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['.RMF']]['file_version'], 4);
|
||||||
|
$RMFchunk .= getid3_lib::BigEndian2String($newHeadersCount, 4);
|
||||||
|
|
||||||
|
$RMFchunk = '.RMF'.getid3_lib::BigEndian2String(strlen($RMFchunk) + 8, 4).$RMFchunk; // .RMF chunk identifier + chunk length
|
||||||
|
return $RMFchunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GeneratePROPchunk(&$chunks, &$new_CONT_tag_data) {
|
||||||
|
$old_CONT_length = 0;
|
||||||
|
$old_DATA_offset = 0;
|
||||||
|
$old_INDX_offset = 0;
|
||||||
|
foreach ($chunks as $key => $chunk) {
|
||||||
|
$chunkNameKeys[$chunk['name']] = $key;
|
||||||
|
if ($chunk['name'] == 'CONT') {
|
||||||
|
$old_CONT_length = $chunk['length'];
|
||||||
|
} elseif ($chunk['name'] == 'DATA') {
|
||||||
|
if (!$old_DATA_offset) {
|
||||||
|
$old_DATA_offset = $chunk['offset'];
|
||||||
|
}
|
||||||
|
} elseif ($chunk['name'] == 'INDX') {
|
||||||
|
if (!$old_INDX_offset) {
|
||||||
|
$old_INDX_offset = $chunk['offset'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$CONTdelta = strlen($new_CONT_tag_data) - $old_CONT_length;
|
||||||
|
|
||||||
|
$PROPchunk = "\x00\x00"; // object version
|
||||||
|
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['max_bit_rate'], 4);
|
||||||
|
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['avg_bit_rate'], 4);
|
||||||
|
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['max_packet_size'], 4);
|
||||||
|
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['avg_packet_size'], 4);
|
||||||
|
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['num_packets'], 4);
|
||||||
|
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['duration'], 4);
|
||||||
|
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['preroll'], 4);
|
||||||
|
$PROPchunk .= getid3_lib::BigEndian2String(max(0, $old_INDX_offset + $CONTdelta), 4);
|
||||||
|
$PROPchunk .= getid3_lib::BigEndian2String(max(0, $old_DATA_offset + $CONTdelta), 4);
|
||||||
|
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['num_streams'], 2);
|
||||||
|
$PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['flags_raw'], 2);
|
||||||
|
|
||||||
|
$PROPchunk = 'PROP'.getid3_lib::BigEndian2String(strlen($PROPchunk) + 8, 4).$PROPchunk; // PROP chunk identifier + chunk length
|
||||||
|
return $PROPchunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GenerateCONTchunk() {
|
||||||
|
foreach ($this->tag_data as $key => $value) {
|
||||||
|
// limit each value to 0xFFFF bytes
|
||||||
|
$this->tag_data[$key] = substr($value, 0, 65535);
|
||||||
|
}
|
||||||
|
|
||||||
|
$CONTchunk = "\x00\x00"; // object version
|
||||||
|
|
||||||
|
$CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['title']) ? strlen($this->tag_data['title']) : 0), 2);
|
||||||
|
$CONTchunk .= (!empty($this->tag_data['title']) ? strlen($this->tag_data['title']) : '');
|
||||||
|
|
||||||
|
$CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['artist']) ? strlen($this->tag_data['artist']) : 0), 2);
|
||||||
|
$CONTchunk .= (!empty($this->tag_data['artist']) ? strlen($this->tag_data['artist']) : '');
|
||||||
|
|
||||||
|
$CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['copyright']) ? strlen($this->tag_data['copyright']) : 0), 2);
|
||||||
|
$CONTchunk .= (!empty($this->tag_data['copyright']) ? strlen($this->tag_data['copyright']) : '');
|
||||||
|
|
||||||
|
$CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['comment']) ? strlen($this->tag_data['comment']) : 0), 2);
|
||||||
|
$CONTchunk .= (!empty($this->tag_data['comment']) ? strlen($this->tag_data['comment']) : '');
|
||||||
|
|
||||||
|
if ($this->paddedlength > (strlen($CONTchunk) + 8)) {
|
||||||
|
$CONTchunk .= str_repeat("\x00", $this->paddedlength - strlen($CONTchunk) - 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
$CONTchunk = 'CONT'.getid3_lib::BigEndian2String(strlen($CONTchunk) + 8, 4).$CONTchunk; // CONT chunk identifier + chunk length
|
||||||
|
|
||||||
|
return $CONTchunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RemoveReal() {
|
||||||
|
// File MUST be writeable - CHMOD(646) at least
|
||||||
|
if (is_writeable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'r+b'))) {
|
||||||
|
|
||||||
|
// Initialize getID3 engine
|
||||||
|
$getID3 = new getID3;
|
||||||
|
$OldThisFileInfo = $getID3->analyze($this->filename);
|
||||||
|
if (empty($OldThisFileInfo['real']['chunks']) && !empty($OldThisFileInfo['real']['old_ra_header'])) {
|
||||||
|
$this->errors[] = 'Cannot remove Real tags from old-style file format';
|
||||||
|
fclose($fp_source);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($OldThisFileInfo['real']['chunks'])) {
|
||||||
|
$this->errors[] = 'Cannot remove Real tags because cannot find DATA chunk in file';
|
||||||
|
fclose($fp_source);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) {
|
||||||
|
$oldChunkInfo[$chunkarray['name']] = $chunkarray;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($oldChunkInfo['CONT'])) {
|
||||||
|
// no existing CONT chunk
|
||||||
|
fclose($fp_source);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$BeforeOffset = $oldChunkInfo['CONT']['offset'];
|
||||||
|
$AfterOffset = $oldChunkInfo['CONT']['offset'] + $oldChunkInfo['CONT']['length'];
|
||||||
|
if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) {
|
||||||
|
if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) {
|
||||||
|
|
||||||
|
rewind($fp_source);
|
||||||
|
fwrite($fp_temp, fread($fp_source, $BeforeOffset));
|
||||||
|
fseek($fp_source, $AfterOffset, SEEK_SET);
|
||||||
|
while ($buffer = fread($fp_source, $this->fread_buffer_size)) {
|
||||||
|
fwrite($fp_temp, $buffer, strlen($buffer));
|
||||||
|
}
|
||||||
|
fclose($fp_temp);
|
||||||
|
|
||||||
|
if (copy($tempfilename, $this->filename)) {
|
||||||
|
unlink($tempfilename);
|
||||||
|
fclose($fp_source);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
unlink($tempfilename);
|
||||||
|
$this->errors[] = 'FAILED: copy('.$tempfilename.', '.$this->filename.')';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose($fp_source);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
121
app/library/getid3/write.vorbiscomment.php
Normal file
121
app/library/getid3/write.vorbiscomment.php
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
<?php
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
/// getID3() by James Heinrich <info@getid3.org> //
|
||||||
|
// available at http://getid3.sourceforge.net //
|
||||||
|
// or http://www.getid3.org //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// See readme.txt for more details //
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// write.vorbiscomment.php //
|
||||||
|
// module for writing VorbisComment tags //
|
||||||
|
// dependencies: /helperapps/vorbiscomment.exe //
|
||||||
|
// ///
|
||||||
|
/////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
class getid3_write_vorbiscomment
|
||||||
|
{
|
||||||
|
|
||||||
|
var $filename;
|
||||||
|
var $tag_data;
|
||||||
|
var $warnings = array(); // any non-critical errors will be stored here
|
||||||
|
var $errors = array(); // any critical errors will be stored here
|
||||||
|
|
||||||
|
function getid3_write_vorbiscomment() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function WriteVorbisComment() {
|
||||||
|
|
||||||
|
if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
|
||||||
|
$this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call vorbiscomment, tags not written';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create file with new comments
|
||||||
|
$tempcommentsfilename = tempnam(GETID3_TEMP_DIR, 'getID3');
|
||||||
|
if (is_writable($tempcommentsfilename) && is_file($tempcommentsfilename) && ($fpcomments = fopen($tempcommentsfilename, 'wb'))) {
|
||||||
|
|
||||||
|
foreach ($this->tag_data as $key => $value) {
|
||||||
|
foreach ($value as $commentdata) {
|
||||||
|
fwrite($fpcomments, $this->CleanVorbisCommentName($key).'='.$commentdata."\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose($fpcomments);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->errors[] = 'failed to open temporary tags file "'.$tempcommentsfilename.'", tags not written';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldignoreuserabort = ignore_user_abort(true);
|
||||||
|
if (GETID3_OS_ISWINDOWS) {
|
||||||
|
|
||||||
|
if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) {
|
||||||
|
//$commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w --raw -c "'.$tempcommentsfilename.'" "'.str_replace('/', '\\', $this->filename).'"';
|
||||||
|
// vorbiscomment works fine if you copy-paste the above commandline into a command prompt,
|
||||||
|
// but refuses to work with `backtick` if there are "doublequotes" present around BOTH
|
||||||
|
// the metaflac pathname and the target filename. For whatever reason...??
|
||||||
|
// The solution is simply ensure that the metaflac pathname has no spaces,
|
||||||
|
// and therefore does not need to be quoted
|
||||||
|
|
||||||
|
// On top of that, if error messages are not always captured properly under Windows
|
||||||
|
// To at least see if there was a problem, compare file modification timestamps before and after writing
|
||||||
|
clearstatcache();
|
||||||
|
$timestampbeforewriting = filemtime($this->filename);
|
||||||
|
|
||||||
|
$commandline = GETID3_HELPERAPPSDIR.'vorbiscomment.exe -w --raw -c "'.$tempcommentsfilename.'" "'.$this->filename.'" 2>&1';
|
||||||
|
$VorbiscommentError = `$commandline`;
|
||||||
|
|
||||||
|
if (empty($VorbiscommentError)) {
|
||||||
|
clearstatcache();
|
||||||
|
if ($timestampbeforewriting == filemtime($this->filename)) {
|
||||||
|
$VorbiscommentError = 'File modification timestamp has not changed - it looks like the tags were not written';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$VorbiscommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$commandline = 'vorbiscomment -w --raw -c "'.$tempcommentsfilename.'" "'.$this->filename.'" 2>&1';
|
||||||
|
$VorbiscommentError = `$commandline`;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove temporary comments file
|
||||||
|
unlink($tempcommentsfilename);
|
||||||
|
ignore_user_abort($oldignoreuserabort);
|
||||||
|
|
||||||
|
if (!empty($VorbiscommentError)) {
|
||||||
|
|
||||||
|
$this->errors[] = 'system call to vorbiscomment failed with message: '."\n\n".$VorbiscommentError;
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DeleteVorbisComment() {
|
||||||
|
$this->tag_data = array(array());
|
||||||
|
return $this->WriteVorbisComment();
|
||||||
|
}
|
||||||
|
|
||||||
|
function CleanVorbisCommentName($originalcommentname) {
|
||||||
|
// A case-insensitive field name that may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded.
|
||||||
|
// ASCII 0x41 through 0x5A inclusive (A-Z) is to be considered equivalent to ASCII 0x61 through
|
||||||
|
// 0x7A inclusive (a-z).
|
||||||
|
|
||||||
|
// replace invalid chars with a space, return uppercase text
|
||||||
|
// Thanks Chris Bolt <chris-getid3Øbolt*cx> for improving this function
|
||||||
|
// note: *reg_replace() replaces nulls with empty string (not space)
|
||||||
|
return strtoupper(preg_replace('#[^ -<>-}]#', ' ', str_replace("\x00", ' ', $originalcommentname)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
BIN
app/library/helperapps/metaflac.exe
Normal file
BIN
app/library/helperapps/metaflac.exe
Normal file
Binary file not shown.
BIN
app/library/helperapps/vorbiscomment.exe
Normal file
BIN
app/library/helperapps/vorbiscomment.exe
Normal file
Binary file not shown.
|
@ -8,6 +8,7 @@
|
||||||
use External;
|
use External;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
class CreateAlbumCommand extends CommandBase {
|
class CreateAlbumCommand extends CommandBase {
|
||||||
private $_input;
|
private $_input;
|
||||||
|
@ -31,12 +32,12 @@
|
||||||
public function execute() {
|
public function execute() {
|
||||||
$rules = [
|
$rules = [
|
||||||
'title' => 'required|min:3|max:50',
|
'title' => 'required|min:3|max:50',
|
||||||
'description' => '',
|
|
||||||
'cover' => 'image|mimes:png|min_width:350|min_height:350',
|
'cover' => 'image|mimes:png|min_width:350|min_height:350',
|
||||||
'cover_id' => 'exists:images,id',
|
'cover_id' => 'exists:images,id',
|
||||||
|
'track_ids' => 'exists:tracks,id'
|
||||||
];
|
];
|
||||||
|
|
||||||
$validator = \Validator::make($this->_input, $rules);
|
$validator = Validator::make($this->_input, $rules);
|
||||||
|
|
||||||
if ($validator->fails())
|
if ($validator->fails())
|
||||||
return CommandResponse::fail($validator);
|
return CommandResponse::fail($validator);
|
||||||
|
@ -52,10 +53,12 @@
|
||||||
else if (isset($this->_input['cover'])) {
|
else if (isset($this->_input['cover'])) {
|
||||||
$cover = $this->_input['cover'];
|
$cover = $this->_input['cover'];
|
||||||
$album->cover_id = Image::upload($cover, Auth::user())->id;
|
$album->cover_id = Image::upload($cover, Auth::user())->id;
|
||||||
} else if ($this->_input['remove_cover'] == 'true')
|
} else if (isset($this->_input['remove_cover']) && $this->_input['remove_cover'] == 'true')
|
||||||
$album->cover_id = null;
|
$album->cover_id = null;
|
||||||
|
|
||||||
|
$trackIds = explode(',', $this->_input['track_ids']);
|
||||||
$album->save();
|
$album->save();
|
||||||
|
$album->syncTrackIds($trackIds);
|
||||||
|
|
||||||
return CommandResponse::succeed(['id' => $album->id]);
|
return CommandResponse::succeed(['id' => $album->id]);
|
||||||
}
|
}
|
||||||
|
|
68
app/models/Commands/CreatePlaylistCommand.php
Normal file
68
app/models/Commands/CreatePlaylistCommand.php
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Commands;
|
||||||
|
|
||||||
|
use Entities\Album;
|
||||||
|
use Entities\Image;
|
||||||
|
use Entities\PinnedPlaylist;
|
||||||
|
use Entities\Playlist;
|
||||||
|
use Entities\Track;
|
||||||
|
use External;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class CreatePlaylistCommand extends CommandBase {
|
||||||
|
private $_input;
|
||||||
|
|
||||||
|
function __construct($input) {
|
||||||
|
$this->_input = $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize() {
|
||||||
|
$user = \Auth::user();
|
||||||
|
return $user != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute() {
|
||||||
|
$rules = [
|
||||||
|
'title' => 'required|min:3|max:50',
|
||||||
|
'is_public' => 'required',
|
||||||
|
'is_pinned' => 'required'
|
||||||
|
];
|
||||||
|
|
||||||
|
$validator = Validator::make($this->_input, $rules);
|
||||||
|
|
||||||
|
if ($validator->fails())
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
|
||||||
|
$playlist = new Playlist();
|
||||||
|
$playlist->user_id = Auth::user()->id;
|
||||||
|
$playlist->title = $this->_input['title'];
|
||||||
|
$playlist->description = $this->_input['description'];
|
||||||
|
$playlist->is_public = $this->_input['is_public'] == 'true';
|
||||||
|
|
||||||
|
$playlist->save();
|
||||||
|
|
||||||
|
if ($this->_input['is_pinned'] == 'true') {
|
||||||
|
$playlist->pin(Auth::user()->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommandResponse::succeed([
|
||||||
|
'id' => $playlist->id,
|
||||||
|
'title' => $playlist->title,
|
||||||
|
'slug' => $playlist->slug,
|
||||||
|
'created_at' => $playlist->created_at,
|
||||||
|
'description' => $playlist->description,
|
||||||
|
'url' => $playlist->url,
|
||||||
|
'is_pinned' => $this->_input['is_pinned'] == 'true',
|
||||||
|
'is_public' => $this->_input['is_public'] == 'true']);
|
||||||
|
}
|
||||||
|
}
|
41
app/models/Commands/DeleteAlbumCommand.php
Normal file
41
app/models/Commands/DeleteAlbumCommand.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Commands;
|
||||||
|
|
||||||
|
use Entities\Album;
|
||||||
|
use Entities\Track;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class DeleteAlbumCommand extends CommandBase {
|
||||||
|
private $_albumId;
|
||||||
|
private $_album;
|
||||||
|
|
||||||
|
function __construct($albumId) {
|
||||||
|
$this->_albumId = $albumId;
|
||||||
|
$this->_album = ALbum::find($albumId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize() {
|
||||||
|
$user = Auth::user();
|
||||||
|
return $this->_album && $user != null && $this->_album->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute() {
|
||||||
|
foreach ($this->_album->tracks as $track) {
|
||||||
|
$track->album_id = null;
|
||||||
|
$track->track_number = null;
|
||||||
|
$track->updateTags();
|
||||||
|
$track->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_album->delete();
|
||||||
|
return CommandResponse::succeed();
|
||||||
|
}
|
||||||
|
}
|
39
app/models/Commands/DeletePlaylistCommand.php
Normal file
39
app/models/Commands/DeletePlaylistCommand.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Commands;
|
||||||
|
|
||||||
|
use Entities\Album;
|
||||||
|
use Entities\Playlist;
|
||||||
|
use Entities\Track;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class DeletePlaylistCommand extends CommandBase {
|
||||||
|
private $_playlistId;
|
||||||
|
private $_playlist;
|
||||||
|
|
||||||
|
function __construct($playlistId) {
|
||||||
|
$this->_playlistId = $playlistId;
|
||||||
|
$this->_playlist = Playlist::find($playlistId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize() {
|
||||||
|
$user = Auth::user();
|
||||||
|
return $this->_playlist && $user != null && $this->_playlist->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute() {
|
||||||
|
foreach ($this->_playlist->pins as $pin) {
|
||||||
|
$pin->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_playlist->delete();
|
||||||
|
return CommandResponse::succeed();
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,15 @@
|
||||||
* @return CommandResponse
|
* @return CommandResponse
|
||||||
*/
|
*/
|
||||||
public function execute() {
|
public function execute() {
|
||||||
$this->_track->delete();
|
if ($this->_track->album_id != null) {
|
||||||
|
$album = $this->_track->album;
|
||||||
|
$this->_track->album_id = null;
|
||||||
|
$this->_track->track_number = null;
|
||||||
|
$this->_track->delete();
|
||||||
|
$album->updateTrackNumbers();
|
||||||
|
} else
|
||||||
|
$this->_track->delete();
|
||||||
|
|
||||||
return CommandResponse::succeed();
|
return CommandResponse::succeed();
|
||||||
}
|
}
|
||||||
}
|
}
|
66
app/models/Commands/EditAlbumCommand.php
Normal file
66
app/models/Commands/EditAlbumCommand.php
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Commands;
|
||||||
|
|
||||||
|
use Entities\Album;
|
||||||
|
use Entities\Image;
|
||||||
|
use Entities\Track;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class EditAlbumCommand extends CommandBase {
|
||||||
|
private $_input;
|
||||||
|
private $_albumId;
|
||||||
|
private $_album;
|
||||||
|
|
||||||
|
function __construct($trackId, $input) {
|
||||||
|
$this->_input = $input;
|
||||||
|
$this->_albumId = $trackId;
|
||||||
|
$this->_album = Album::find($trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize() {
|
||||||
|
$user = Auth::user();
|
||||||
|
return $this->_album && $user != null && $this->_album->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute() {
|
||||||
|
$rules = [
|
||||||
|
'title' => 'required|min:3|max:50',
|
||||||
|
'cover' => 'image|mimes:png|min_width:350|min_height:350',
|
||||||
|
'cover_id' => 'exists:images,id',
|
||||||
|
'track_ids' => 'exists:tracks,id'
|
||||||
|
];
|
||||||
|
|
||||||
|
$validator = Validator::make($this->_input, $rules);
|
||||||
|
|
||||||
|
if ($validator->fails())
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
|
||||||
|
$this->_album->title = $this->_input['title'];
|
||||||
|
$this->_album->description = $this->_input['description'];
|
||||||
|
|
||||||
|
if (isset($this->_input['cover_id'])) {
|
||||||
|
$this->_album->cover_id = $this->_input['cover_id'];
|
||||||
|
}
|
||||||
|
else if (isset($this->_input['cover'])) {
|
||||||
|
$cover = $this->_input['cover'];
|
||||||
|
$this->_album->cover_id = Image::upload($cover, Auth::user())->id;
|
||||||
|
} else if (isset($this->_input['remove_cover']) && $this->_input['remove_cover'] == 'true')
|
||||||
|
$this->_album->cover_id = null;
|
||||||
|
|
||||||
|
$trackIds = explode(',', $this->_input['track_ids']);
|
||||||
|
$this->_album->syncTrackIds($trackIds);
|
||||||
|
$this->_album->save();
|
||||||
|
|
||||||
|
return CommandResponse::succeed();
|
||||||
|
}
|
||||||
|
}
|
72
app/models/Commands/EditPlaylistCommand.php
Normal file
72
app/models/Commands/EditPlaylistCommand.php
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Commands;
|
||||||
|
|
||||||
|
use Entities\Album;
|
||||||
|
use Entities\Image;
|
||||||
|
use Entities\PinnedPlaylist;
|
||||||
|
use Entities\Playlist;
|
||||||
|
use Entities\Track;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class EditPlaylistCommand extends CommandBase {
|
||||||
|
private $_input;
|
||||||
|
private $_playlistId;
|
||||||
|
private $_playlist;
|
||||||
|
|
||||||
|
function __construct($playlistId, $input) {
|
||||||
|
$this->_input = $input;
|
||||||
|
$this->_playlistId = $playlistId;
|
||||||
|
$this->_playlist = Playlist::find($playlistId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize() {
|
||||||
|
$user = Auth::user();
|
||||||
|
return $this->_playlist && $user != null && $this->_playlist->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute() {
|
||||||
|
$rules = [
|
||||||
|
'title' => 'required|min:3|max:50',
|
||||||
|
'is_public' => 'required',
|
||||||
|
'is_pinned' => 'required'
|
||||||
|
];
|
||||||
|
|
||||||
|
$validator = Validator::make($this->_input, $rules);
|
||||||
|
|
||||||
|
if ($validator->fails())
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
|
||||||
|
$this->_playlist->title = $this->_input['title'];
|
||||||
|
$this->_playlist->description = $this->_input['description'];
|
||||||
|
$this->_playlist->is_public = $this->_input['is_public'] == 'true';
|
||||||
|
|
||||||
|
$this->_playlist->save();
|
||||||
|
|
||||||
|
$pin = PinnedPlaylist::whereUserId(Auth::user()->id)->wherePlaylistId($this->_playlistId)->first();
|
||||||
|
if ($pin && $this->_input['is_pinned'] != 'true')
|
||||||
|
$pin->delete();
|
||||||
|
else if (!$pin && $this->_input['is_pinned'] == 'true') {
|
||||||
|
$this->_playlist->pin(Auth::user()->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommandResponse::succeed([
|
||||||
|
'id' => $this->_playlist->id,
|
||||||
|
'title' => $this->_playlist->title,
|
||||||
|
'slug' => $this->_playlist->slug,
|
||||||
|
'created_at' => $this->_playlist->created_at,
|
||||||
|
'description' => $this->_playlist->description,
|
||||||
|
'url' => $this->_playlist->url,
|
||||||
|
'is_pinned' => $this->_input['is_pinned'] == 'true',
|
||||||
|
'is_public' => $this->_input['is_public'] == 'true']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Commands;
|
namespace Commands;
|
||||||
|
|
||||||
|
use Entities\Album;
|
||||||
use Entities\Image;
|
use Entities\Image;
|
||||||
use Entities\Track;
|
use Entities\Track;
|
||||||
use External;
|
use External;
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
'track_type_id' => 'required|exists:track_types,id',
|
'track_type_id' => 'required|exists:track_types,id',
|
||||||
'songs' => 'required_when:track_type,2|exists:songs,id',
|
'songs' => 'required_when:track_type,2|exists:songs,id',
|
||||||
'cover_id' => 'exists:images,id',
|
'cover_id' => 'exists:images,id',
|
||||||
|
'album_id' => 'exists:albums,id'
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($this->_input['track_type_id'] == 2)
|
if ($this->_input['track_type_id'] == 2)
|
||||||
|
@ -66,6 +68,24 @@
|
||||||
$track->is_downloadable = $this->_input['is_downloadable'] == 'true';
|
$track->is_downloadable = $this->_input['is_downloadable'] == 'true';
|
||||||
$track->is_vocal = $isVocal;
|
$track->is_vocal = $isVocal;
|
||||||
|
|
||||||
|
if (isset($this->_input['album_id']) && strlen(trim($this->_input['album_id']))) {
|
||||||
|
if ($track->album_id != null && $track->album_id != $this->_input['album_id'])
|
||||||
|
$this->removeTrackFromAlbum($track);
|
||||||
|
|
||||||
|
if ($track->album_id != $this->_input['album_id']) {
|
||||||
|
$album = Album::find($this->_input['album_id']);
|
||||||
|
$track->track_number = $album->tracks()->count() + 1;
|
||||||
|
$track->album_id = $this->_input['album_id'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($track->album_id != null) {
|
||||||
|
$this->removeTrackFromAlbum($track);
|
||||||
|
}
|
||||||
|
|
||||||
|
$track->track_number = null;
|
||||||
|
$track->album_id = null;
|
||||||
|
}
|
||||||
|
|
||||||
if ($track->track_type_id == 2) {
|
if ($track->track_type_id == 2) {
|
||||||
$track->showSongs()->sync(explode(',', $this->_input['show_song_ids']));
|
$track->showSongs()->sync(explode(',', $this->_input['show_song_ids']));
|
||||||
} else
|
} else
|
||||||
|
@ -84,8 +104,23 @@
|
||||||
} else if ($this->_input['remove_cover'] == 'true')
|
} else if ($this->_input['remove_cover'] == 'true')
|
||||||
$track->cover_id = null;
|
$track->cover_id = null;
|
||||||
|
|
||||||
|
$track->updateTags();
|
||||||
$track->save();
|
$track->save();
|
||||||
|
|
||||||
return CommandResponse::succeed();
|
return CommandResponse::succeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function removeTrackFromAlbum($track) {
|
||||||
|
$album = $track->album;
|
||||||
|
$index = 0;
|
||||||
|
|
||||||
|
foreach ($album->tracks as $track) {
|
||||||
|
if ($track->id == $this->_trackId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$track->track_number = ++$index;
|
||||||
|
$track->updateTags();
|
||||||
|
$track->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Commands;
|
namespace Commands;
|
||||||
|
|
||||||
use Entities\Track;
|
use Entities\Track;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class UploadTrackCommand extends CommandBase {
|
class UploadTrackCommand extends CommandBase {
|
||||||
/**
|
/**
|
||||||
|
@ -38,7 +39,6 @@
|
||||||
try {
|
try {
|
||||||
$track->user_id = $user->id;
|
$track->user_id = $user->id;
|
||||||
$track->title = pathinfo($trackFile->getClientOriginalName(), PATHINFO_FILENAME);
|
$track->title = pathinfo($trackFile->getClientOriginalName(), PATHINFO_FILENAME);
|
||||||
$track->slug = \Str::slug($track->title);
|
|
||||||
$track->duration = $audio->getDuration();
|
$track->duration = $audio->getDuration();
|
||||||
|
|
||||||
$track->save();
|
$track->save();
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
$command = str_replace('{$source}', '"' . $source . '"', $command);
|
$command = str_replace('{$source}', '"' . $source . '"', $command);
|
||||||
$command = str_replace('{$target}', '"' . $target . '"', $command);
|
$command = str_replace('{$target}', '"' . $target . '"', $command);
|
||||||
|
|
||||||
\Log::info('Encoding ' . $track->id . ' into ' . $target);
|
Log::info('Encoding ' . $track->id . ' into ' . $target);
|
||||||
$this->notify('Encoding ' . $name, $index / count(Track::$Formats) * 100);
|
$this->notify('Encoding ' . $name, $index / count(Track::$Formats) * 100);
|
||||||
|
|
||||||
$pipes = [];
|
$pipes = [];
|
||||||
|
@ -69,6 +69,8 @@
|
||||||
foreach ($processes as $proc)
|
foreach ($processes as $proc)
|
||||||
proc_close($proc);
|
proc_close($proc);
|
||||||
|
|
||||||
|
$track->updateTags();
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$track->delete();
|
$track->delete();
|
||||||
throw $e;
|
throw $e;
|
||||||
|
|
|
@ -4,10 +4,13 @@
|
||||||
|
|
||||||
use Cover;
|
use Cover;
|
||||||
use Whoops\Example\Exception;
|
use Whoops\Example\Exception;
|
||||||
|
use Traits\SlugTrait;
|
||||||
|
|
||||||
class Album extends \Eloquent {
|
class Album extends \Eloquent {
|
||||||
protected $softDelete = true;
|
protected $softDelete = true;
|
||||||
|
|
||||||
|
use SlugTrait;
|
||||||
|
|
||||||
public static function summary() {
|
public static function summary() {
|
||||||
return self::select('id', 'title', 'user_id', 'slug', 'created_at', 'cover_id');
|
return self::select('id', 'title', 'user_id', 'slug', 'created_at', 'cover_id');
|
||||||
}
|
}
|
||||||
|
@ -22,6 +25,10 @@
|
||||||
return $this->belongsTo('Entities\Image');
|
return $this->belongsTo('Entities\Image');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tracks() {
|
||||||
|
return $this->hasMany('Entities\Track')->orderBy('track_number', 'asc');
|
||||||
|
}
|
||||||
|
|
||||||
public function hasCover() {
|
public function hasCover() {
|
||||||
return $this->cover_id != null;
|
return $this->cover_id != null;
|
||||||
}
|
}
|
||||||
|
@ -49,4 +56,76 @@
|
||||||
$format = Track::$Formats[$format];
|
$format = Track::$Formats[$format];
|
||||||
return "{$this->id}.{$format['extension']}.zip";
|
return "{$this->id}.{$format['extension']}.zip";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updateTrackNumbers() {
|
||||||
|
$tracks = Track::whereAlbumId($this->id)->get();
|
||||||
|
$index = 1;
|
||||||
|
|
||||||
|
foreach ($tracks as $track) {
|
||||||
|
$track->track_number = $index;
|
||||||
|
$index++;
|
||||||
|
$track->updateTags();
|
||||||
|
$track->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncTrackIds($trackIds) {
|
||||||
|
$trackIdsInAlbum = [];
|
||||||
|
foreach ($this->tracks as $track) {
|
||||||
|
$trackIdsInAlbum[] = $track->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$trackIdsCount = count($trackIds);
|
||||||
|
$trackIdsInAlbumCount = count($trackIdsInAlbum);
|
||||||
|
$isSame = true;
|
||||||
|
|
||||||
|
if ($trackIdsInAlbumCount != $trackIdsCount)
|
||||||
|
$isSame = false;
|
||||||
|
else
|
||||||
|
for ($i = 0; $i < $trackIdsInAlbumCount; $i++) {
|
||||||
|
if ($i >= $trackIdsCount || $trackIdsInAlbum[$i] != $trackIds[$i]) {
|
||||||
|
$isSame = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isSame)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$index = 1;
|
||||||
|
$tracksToRemove = [];
|
||||||
|
$albumsToFix = [];
|
||||||
|
|
||||||
|
foreach ($this->tracks as $track)
|
||||||
|
$tracksToRemove[$track->id] = $track;
|
||||||
|
|
||||||
|
foreach ($trackIds as $trackId) {
|
||||||
|
if (!strlen(trim($trackId)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$track = Track::find($trackId);
|
||||||
|
if ($track->album_id != null && $track->album_id != $this->id) {
|
||||||
|
$albumsToFix[] = $track->album;
|
||||||
|
}
|
||||||
|
|
||||||
|
$track->album_id = $this->id;
|
||||||
|
$track->track_number = $index;
|
||||||
|
$track->updateTags();
|
||||||
|
$track->save();
|
||||||
|
|
||||||
|
unset($tracksToRemove[$track->id]);
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($tracksToRemove as $track) {
|
||||||
|
$track->album_id = null;
|
||||||
|
$track->track_number = null;
|
||||||
|
$track->updateTags();
|
||||||
|
$track->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($albumsToFix as $album) {
|
||||||
|
$album->updateTrackNumbers();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Entities;
|
namespace Entities;
|
||||||
|
use Traits\SlugTrait;
|
||||||
|
|
||||||
class Genre extends \Eloquent {
|
class Genre extends \Eloquent {
|
||||||
protected $table = 'genres';
|
protected $table = 'genres';
|
||||||
|
|
||||||
|
use SlugTrait;
|
||||||
}
|
}
|
16
app/models/Entities/PinnedPlaylist.php
Normal file
16
app/models/Entities/PinnedPlaylist.php
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Entities;
|
||||||
|
use Traits\SlugTrait;
|
||||||
|
|
||||||
|
class PinnedPlaylist extends \Eloquent {
|
||||||
|
protected $table = 'pinned_playlists';
|
||||||
|
|
||||||
|
public function user() {
|
||||||
|
return $this->belongsTo('Entities\User');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function playlist() {
|
||||||
|
return $this->belongsTo('Entities\Playlist');
|
||||||
|
}
|
||||||
|
}
|
58
app/models/Entities/Playlist.php
Normal file
58
app/models/Entities/Playlist.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Entities;
|
||||||
|
use Traits\SlugTrait;
|
||||||
|
|
||||||
|
class Playlist extends \Eloquent {
|
||||||
|
protected $table = 'playlists';
|
||||||
|
protected $softDelete = true;
|
||||||
|
|
||||||
|
use SlugTrait;
|
||||||
|
|
||||||
|
public static function summary() {
|
||||||
|
return self::select('id', 'title', 'user_id', 'slug', 'created_at', 'is_public', 'description');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tracks() {
|
||||||
|
return $this->belongsToMany('Entities\Track')->orderBy('position', 'asc');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pins() {
|
||||||
|
return $this->hasMany('Entities\PinnedPlaylist');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user() {
|
||||||
|
return $this->belongsTo('Entities\User');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasPinFor($userId) {
|
||||||
|
foreach ($this->pins as $pin) {
|
||||||
|
if ($pin->user_id == $userId)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canView($user) {
|
||||||
|
return $this->is_public || ($user != null && $user->id == $this->user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUrlAttribute() {
|
||||||
|
return '/playlist/' . $this->id . '/' . $this->slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCoverUrl($type = Image::NORMAL) {
|
||||||
|
if ($this->tracks->count() == 0)
|
||||||
|
return $this->user->getAvatarUrl($type);
|
||||||
|
|
||||||
|
return $this->tracks[0]->getCoverUrl($type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pin($userId) {
|
||||||
|
$pin = new PinnedPlaylist();
|
||||||
|
$pin->playlist_id = $this->id;
|
||||||
|
$pin->user_id = $userId;
|
||||||
|
$pin->save();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,25 +3,66 @@
|
||||||
namespace Entities;
|
namespace Entities;
|
||||||
|
|
||||||
use Cover;
|
use Cover;
|
||||||
|
use External;
|
||||||
|
use getid3_writetags;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Whoops\Example\Exception;
|
use Whoops\Example\Exception;
|
||||||
|
use Traits\SlugTrait;
|
||||||
|
|
||||||
class Track extends \Eloquent {
|
class Track extends \Eloquent {
|
||||||
protected $softDelete = true;
|
protected $softDelete = true;
|
||||||
|
|
||||||
|
use SlugTrait;
|
||||||
|
|
||||||
public static $Formats = [
|
public static $Formats = [
|
||||||
'FLAC' => ['extension' => 'flac', 'tag_format' => 'metaflac', 'mime_type' => 'audio/flac', 'command' => 'ffmpeg 2>&1 -y -i {$source} -acodec flac -aq 8 -f flac {$target}'],
|
'FLAC' => ['extension' => 'flac', 'tag_format' => 'metaflac', 'tag_method' => 'updateTagsWithGetId3', 'mime_type' => 'audio/flac', 'command' => 'ffmpeg 2>&1 -y -i {$source} -acodec flac -aq 8 -f flac {$target}'],
|
||||||
'MP3' => ['extension' => 'mp3', 'tag_format' => 'id3v2.3', 'mime_type' => 'audio/mpeg', 'command' => 'ffmpeg 2>&1 -y -i {$source} -acodec libmp3lame -ab 320k -f mp3 {$target}'],
|
'MP3' => ['extension' => 'mp3', 'tag_format' => 'id3v2.3', 'tag_method' => 'updateTagsWithGetId3', 'mime_type' => 'audio/mpeg', 'command' => 'ffmpeg 2>&1 -y -i {$source} -acodec libmp3lame -ab 320k -f mp3 {$target}'],
|
||||||
'OGG Vorbis' => ['extension' => 'ogg', 'tag_format' => 'vorbiscomment', 'mime_type' => 'audio/ogg', 'command' => 'ffmpeg 2>&1 -y -i {$source} -acodec libvorbis -aq 7 -f ogg {$target}'],
|
'OGG Vorbis' => ['extension' => 'ogg', 'tag_format' => 'vorbiscomment', 'tag_method' => 'updateTagsWithGetId3', 'mime_type' => 'audio/ogg', 'command' => 'ffmpeg 2>&1 -y -i {$source} -acodec libvorbis -aq 7 -f ogg {$target}'],
|
||||||
'AAC' => ['extension' => 'm4a', 'tag_format' => 'AtomicParsley', 'mime_type' => 'audio/mp4', 'command' => 'ffmpeg 2>&1 -y -i {$source} -acodec libfaac -ab 256k -f mp4 {$target}'],
|
'AAC' => ['extension' => 'm4a', 'tag_format' => 'AtomicParsley', 'tag_method' => 'updateTagsWithAtomicParsley', 'mime_type' => 'audio/mp4', 'command' => 'ffmpeg 2>&1 -y -i {$source} -acodec libfaac -ab 256k -f mp4 {$target}'],
|
||||||
'ALAC' => ['extension' => 'alac.m4a', 'tag_format' => 'AtomicParsley', 'mime_type' => 'audio/mp4', 'command' => 'ffmpeg 2>&1 -y -i {$source} -acodec alac {$target}'],
|
'ALAC' => ['extension' => 'alac.m4a', 'tag_format' => 'AtomicParsley', 'tag_method' => 'updateTagsWithAtomicParsley', 'mime_type' => 'audio/mp4', 'command' => 'ffmpeg 2>&1 -y -i {$source} -acodec alac {$target}'],
|
||||||
];
|
];
|
||||||
|
|
||||||
public static function summary() {
|
public static function summary() {
|
||||||
return self::select('id', 'title', 'user_id', 'slug', 'is_vocal', 'is_explicit', 'created_at', 'published_at', 'duration', 'is_downloadable', 'genre_id', 'track_type_id', 'cover_id');
|
return self::select('id', 'title', 'user_id', 'slug', 'is_vocal', 'is_explicit', 'created_at', 'published_at', 'duration', 'is_downloadable', 'genre_id', 'track_type_id', 'cover_id', 'album_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $table = 'tracks';
|
protected $table = 'tracks';
|
||||||
|
|
||||||
|
public function genre() {
|
||||||
|
return $this->belongsTo('Entities\Genre');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cover() {
|
||||||
|
return $this->belongsTo('Entities\Image');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showSongs() {
|
||||||
|
return $this->belongsToMany('Entities\ShowSong');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user() {
|
||||||
|
return $this->belongsTo('Entities\User');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function album() {
|
||||||
|
return $this->belongsTo('Entities\Album');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getYear() {
|
||||||
|
return date('Y', strtotime($this->release_date));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReleaseDate() {
|
||||||
|
if($this->attributes['released_at'] !== NULL)
|
||||||
|
return $this->attributes['released_at'];
|
||||||
|
|
||||||
|
if ($this->attributes['published_at'] !== NULL)
|
||||||
|
return Str::limit($this->$this->attributes['published_at'], 10, '');
|
||||||
|
|
||||||
|
return Str::limit($this->attributes['created_at'], 10, '');
|
||||||
|
}
|
||||||
|
|
||||||
public function ensureDirectoryExists() {
|
public function ensureDirectoryExists() {
|
||||||
$destination = $this->getDirectory();
|
$destination = $this->getDirectory();
|
||||||
|
|
||||||
|
@ -33,21 +74,17 @@
|
||||||
return $this->cover_id != null;
|
return $this->cover_id != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cover() {
|
|
||||||
return $this->belongsTo('Entities\Image');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function showSongs() {
|
|
||||||
return $this->belongsToMany('Entities\ShowSong');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isPublished() {
|
public function isPublished() {
|
||||||
return $this->published_at != null && $this->deleted_at == null;
|
return $this->published_at != null && $this->deleted_at == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCoverUrl($type = Image::NORMAL) {
|
public function getCoverUrl($type = Image::NORMAL) {
|
||||||
if (!$this->hasCover())
|
if (!$this->hasCover()) {
|
||||||
|
if ($this->album_id != null)
|
||||||
|
return $this->album->getCoverUrl($type);
|
||||||
|
|
||||||
return $this->user->getAvatarUrl($type);
|
return $this->user->getAvatarUrl($type);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->cover->getUrl($type);
|
return $this->cover->getUrl($type);
|
||||||
}
|
}
|
||||||
|
@ -61,10 +98,6 @@
|
||||||
return ['created_at', 'deleted_at', 'published_at', 'released_at'];
|
return ['created_at', 'deleted_at', 'published_at', 'released_at'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function user() {
|
|
||||||
return $this->belongsTo('Entities\User');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFilenameFor($format) {
|
public function getFilenameFor($format) {
|
||||||
if (!isset(self::$Formats[$format]))
|
if (!isset(self::$Formats[$format]))
|
||||||
throw new Exception("$format is not a valid format!");
|
throw new Exception("$format is not a valid format!");
|
||||||
|
@ -72,4 +105,95 @@
|
||||||
$format = self::$Formats[$format];
|
$format = self::$Formats[$format];
|
||||||
return "{$this->id}.{$format['extension']}";
|
return "{$this->id}.{$format['extension']}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFileFor($format) {
|
||||||
|
if (!isset(self::$Formats[$format]))
|
||||||
|
throw new Exception("$format is not a valid format!");
|
||||||
|
|
||||||
|
$format = self::$Formats[$format];
|
||||||
|
return "{$this->getDirectory()}/{$this->id}.{$format['extension']}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateTags() {
|
||||||
|
foreach (self::$Formats as $format => $data) {
|
||||||
|
$this->{$data['tag_method']}($format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @noinspection PhpUnusedPrivateMethodInspection */
|
||||||
|
private function updateTagsWithAtomicParsley($format) {
|
||||||
|
$command = 'AtomicParsley "' . $this->getFileFor($format) . '" ';
|
||||||
|
$command .= '--title ' . escapeshellarg($this->title) . ' ';
|
||||||
|
$command .= '--artist ' . escapeshellarg($this->user->display_name) . ' ';
|
||||||
|
$command .= '--year "' . $this->year . '" ';
|
||||||
|
$command .= '--genre ' . escapeshellarg($this->genre != null ? $this->genre->title : '') . ' ';
|
||||||
|
$command .= '--copyright ' . escapeshellarg('© '.$this->year.' '.$this->user->display_name).' ';
|
||||||
|
$command .= '--comment "' . 'Downloaded from: https://pony.fm/' . '" ';
|
||||||
|
$command .= '--encodingTool "' . 'Pony.fm' . '" ';
|
||||||
|
$command .= '--encodedBy "' . 'Pony.fm - https://pony.fm/' . '" ';
|
||||||
|
|
||||||
|
if ($this->album_id !== NULL) {
|
||||||
|
$command .= '--album ' . escapeshellarg($this->album->title) . ' ';
|
||||||
|
$command .= '--tracknum ' . $this->track_number . ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->cover !== NULL) {
|
||||||
|
$command .= '--artwork ' . $this->getCoverUrl() . ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$command .= '--overWrite';
|
||||||
|
|
||||||
|
External::execute($command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @noinspection PhpUnusedPrivateMethodInspection */
|
||||||
|
private function updateTagsWithGetId3($format) {
|
||||||
|
require_once(app_path() . '/library/getid3/getid3.php');
|
||||||
|
require_once(app_path() . '/library/getid3/write.php');
|
||||||
|
$tagWriter = new getid3_writetags;
|
||||||
|
|
||||||
|
$tagWriter->overwrite_tags = true;
|
||||||
|
$tagWriter->tag_encoding = 'UTF-8';
|
||||||
|
$tagWriter->remove_other_tags = true;
|
||||||
|
|
||||||
|
$tagWriter->tag_data = [
|
||||||
|
'title' => [$this->title],
|
||||||
|
'artist' => [$this->user->display_name],
|
||||||
|
'year' => ['' . $this->year],
|
||||||
|
'genre' => [$this->genre != null ? $this->genre->title : ''],
|
||||||
|
'comment' => ['Downloaded from: https://pony.fm/'],
|
||||||
|
'copyright' => ['© ' . $this->year . ' ' . $this->user->display_name],
|
||||||
|
'publisher' => ['Pony.fm - https://pony.fm/'],
|
||||||
|
'encoded_by' => ['https://pony.fm/'],
|
||||||
|
// 'url_artist' => [$this->user->url],
|
||||||
|
// 'url_source' => [$this->url],
|
||||||
|
// 'url_file' => [$this->url],
|
||||||
|
'url_publisher' => ['https://pony.fm/']
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->album_id !== NULL) {
|
||||||
|
$tagWriter->tag_data['album'] = [$this->album->title];
|
||||||
|
$tagWriter->tag_data['track'] = [$this->track_number];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($format == 'MP3' && $this->cover_id != NULL && is_file($this->cover->file)) {
|
||||||
|
$tagWriter->tag_data['attached_picture'][0] = [
|
||||||
|
'data' => file_get_contents($this->cover->file),
|
||||||
|
'picturetypeid' => 2,
|
||||||
|
'description' => 'cover',
|
||||||
|
'mime' => 'image/png'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$tagWriter->filename = $this->getFileFor($format);
|
||||||
|
$tagWriter->tagformats = [self::$Formats[$format]['tag_format']];
|
||||||
|
|
||||||
|
if ($tagWriter->WriteTags()) {
|
||||||
|
if (!empty($tagWriter->warnings)) {
|
||||||
|
Log::warning('There were some warnings:<br />' . implode('<br /><br />', $tagWriter->warnings));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log::error('Failed to write tags!<br />' . implode('<br /><br />', $tagWriter->errors));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
12
app/models/Traits/SlugTrait.php
Normal file
12
app/models/Traits/SlugTrait.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Traits;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
trait SlugTrait {
|
||||||
|
public function setTitleAttribute($value) {
|
||||||
|
$this->slug = Str::slug($value);
|
||||||
|
$this->attributes['title'] = $value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,15 +29,27 @@
|
||||||
|
|
||||||
Route::get('u{id}/avatar_{type}.png', 'UsersController@getAvatar');
|
Route::get('u{id}/avatar_{type}.png', 'UsersController@getAvatar');
|
||||||
|
|
||||||
|
Route::get('playlist/{id}/{slug}', 'PlaylistsController@getPlaylist');
|
||||||
|
Route::get('playlist/{id}-{slug}', 'PlaylistsController@getPlaylist');
|
||||||
|
Route::get('p{id}', 'PlaylistsController@getShortlink');
|
||||||
|
|
||||||
Route::group(['prefix' => 'api/web'], function() {
|
Route::group(['prefix' => 'api/web'], function() {
|
||||||
Route::get('/taxonomies/all', 'Api\Web\TaxonomiesController@getAll');
|
Route::get('/taxonomies/all', 'Api\Web\TaxonomiesController@getAll');
|
||||||
|
|
||||||
|
Route::get('/playlists/show/{id}', 'Api\Web\PlaylistsController@getShow');
|
||||||
|
|
||||||
Route::group(['before' => 'auth|csrf'], function() {
|
Route::group(['before' => 'auth|csrf'], function() {
|
||||||
Route::post('/tracks/upload', 'Api\Web\TracksController@postUpload');
|
Route::post('/tracks/upload', 'Api\Web\TracksController@postUpload');
|
||||||
Route::post('/tracks/delete/{id}', 'Api\Web\TracksController@postDelete');
|
Route::post('/tracks/delete/{id}', 'Api\Web\TracksController@postDelete');
|
||||||
Route::post('/tracks/edit/{id}', 'Api\Web\TracksController@putEdit');
|
Route::post('/tracks/edit/{id}', 'Api\Web\TracksController@postEdit');
|
||||||
|
|
||||||
Route::post('/albums/create', 'Api\Web\AlbumsController@postCreate');
|
Route::post('/albums/create', 'Api\Web\AlbumsController@postCreate');
|
||||||
|
Route::post('/albums/delete/{id}', 'Api\Web\AlbumsController@postDelete');
|
||||||
|
Route::post('/albums/edit/{id}', 'Api\Web\AlbumsController@postEdit');
|
||||||
|
|
||||||
|
Route::post('/playlists/create', 'Api\Web\PlaylistsController@postCreate');
|
||||||
|
Route::post('/playlists/delete/{id}', 'Api\Web\PlaylistsController@postDelete');
|
||||||
|
Route::post('/playlists/edit/{id}', 'Api\Web\PlaylistsController@postEdit');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['before' => 'auth'], function() {
|
Route::group(['before' => 'auth'], function() {
|
||||||
|
@ -48,6 +60,9 @@
|
||||||
|
|
||||||
Route::get('/albums/owned', 'Api\Web\AlbumsController@getOwned');
|
Route::get('/albums/owned', 'Api\Web\AlbumsController@getOwned');
|
||||||
Route::get('/albums/edit/{id}', 'Api\Web\AlbumsController@getEdit');
|
Route::get('/albums/edit/{id}', 'Api\Web\AlbumsController@getEdit');
|
||||||
|
|
||||||
|
Route::get('/playlists/owned', 'Api\Web\PlaylistsController@getOwned');
|
||||||
|
Route::get('/playlists/pinned', 'Api\Web\PlaylistsController@getPinned');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['before' => 'csrf'], function(){
|
Route::group(['before' => 'csrf'], function(){
|
||||||
|
|
6
app/views/playlists/show.blade.php
Normal file
6
app/views/playlists/show.blade.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
@extends('shared._app_layout')
|
||||||
|
|
||||||
|
@section('app_content')
|
||||||
|
<h1>A Playlist!</h1>
|
||||||
|
<p>This page should be what search engines see</p>
|
||||||
|
@endsection
|
|
@ -26,7 +26,7 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="site-body">
|
<div class="site-body">
|
||||||
<section class="sidebar">
|
<section class="sidebar" ng-controller="sidebar">
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><h3>Discover</h3></li>
|
<li><h3>Discover</h3></li>
|
||||||
|
@ -36,11 +36,30 @@
|
||||||
<li ng-class="{selected: $state.includes('artists')}"><a href="/artists">Artists <i class="icon-user"></i></a></li>
|
<li ng-class="{selected: $state.includes('artists')}"><a href="/artists">Artists <i class="icon-user"></i></a></li>
|
||||||
|
|
||||||
@if (Auth::check())
|
@if (Auth::check())
|
||||||
<li><h3>Playlists</h3></li>
|
<li>
|
||||||
<li class="none"><span>no playlists</span></li>
|
<h3>
|
||||||
|
<a href="#" ng-click="createPlaylist()" pfm-eat-click title="Create Playlist"><i class="icon-plus"></i></a>
|
||||||
|
<a href="/account/playlists" ng-class="{selected: $state.is('account-content-playlists')}" title="View Playlists" class="view-all"><i class="icon-list"></i></a>
|
||||||
|
Playlists
|
||||||
|
</h3>
|
||||||
|
</li>
|
||||||
|
<li class="none" ng-show="!playlists.length"><span>no pinned playlists</span></li>
|
||||||
|
<li class="dropdown" ng-repeat="playlist in playlists" ng-cloak ng-class="{selected: $state.is('playlist') && $state.params.id == playlist.id}">
|
||||||
|
<a class="menu dropdown-toggle" pfm-eat-click href="#"><i class="icon-ellipsis-vertical"></i></a>
|
||||||
|
<a href="{{Helpers::angular('playlist.url')}}" ng-bind="playlist.title"></a>
|
||||||
|
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="#" pfm-eat-click ng-click="editPlaylist(playlist)">Edit</a></li>
|
||||||
|
<li><a href="#" pfm-eat-click ng-click="unpinPlaylist(playlist)">Unpin</a></li>
|
||||||
|
<li><a href="#" pfm-eat-click ng-click="deletePlaylist(playlist)" ng-show="playlist.user_id == auth.user_id">Delete</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<h3>Account</h3>
|
<h3>
|
||||||
|
<a href="#" title="Upload Track"><i class="icon-upload"></i></a>
|
||||||
|
Account
|
||||||
|
</h3>
|
||||||
</li>
|
</li>
|
||||||
<li ng-class="{selected: $state.includes('account-favourites')}"><a href="/account/favourites">Favourites</a></li>
|
<li ng-class="{selected: $state.includes('account-favourites')}"><a href="/account/favourites">Favourites</a></li>
|
||||||
<li ng-class="{selected: $state.includes('account-content')}"><a href="/account/tracks">Your Content</a></li>
|
<li ng-class="{selected: $state.includes('account-content')}"><a href="/account/tracks">Your Content</a></li>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date'], [
|
angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date', 'ui.sortable'], [
|
||||||
'$routeProvider', '$locationProvider', '$stateProvider', '$dialogProvider'
|
'$routeProvider', '$locationProvider', '$stateProvider', '$dialogProvider'
|
||||||
(route, location, state, $dialogProvider) ->
|
(route, location, state, $dialogProvider) ->
|
||||||
|
|
||||||
|
@ -37,9 +37,10 @@ angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date'], [
|
||||||
templateUrl: '/templates/account/content/album.html'
|
templateUrl: '/templates/account/content/album.html'
|
||||||
controller: 'account-albums-edit'
|
controller: 'account-albums-edit'
|
||||||
|
|
||||||
state.state 'account-content.playlists',
|
state.state 'account-content-playlists',
|
||||||
url: '/playlists'
|
url: '/account/playlists'
|
||||||
templateUrl: '/templates/account/content/playlists.html'
|
templateUrl: '/templates/account/content/playlists.html'
|
||||||
|
controller: 'account-playlists'
|
||||||
|
|
||||||
state.state 'account-favourites',
|
state.state 'account-favourites',
|
||||||
url: '/account/favourites'
|
url: '/account/favourites'
|
||||||
|
@ -89,6 +90,11 @@ angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date'], [
|
||||||
url: '/playlists'
|
url: '/playlists'
|
||||||
templateUrl: '/templates/playlists/index.html'
|
templateUrl: '/templates/playlists/index.html'
|
||||||
|
|
||||||
|
state.state 'playlist',
|
||||||
|
url: '/playlist/:id/:slug'
|
||||||
|
templateUrl: '/templates/playlists/show.html'
|
||||||
|
controller: 'playlist'
|
||||||
|
|
||||||
# Artists
|
# Artists
|
||||||
|
|
||||||
state.state 'artists',
|
state.state 'artists',
|
||||||
|
|
|
@ -7,21 +7,56 @@ angular.module('ponyfm').controller "account-albums-edit", [
|
||||||
$scope.isDirty = false
|
$scope.isDirty = false
|
||||||
$scope.album = {}
|
$scope.album = {}
|
||||||
$scope.isSaving = false
|
$scope.isSaving = false
|
||||||
|
$scope.tracks = []
|
||||||
|
$scope.trackIds = {}
|
||||||
|
|
||||||
|
$scope.toggleTrack = (track) ->
|
||||||
|
if $scope.trackIds[track.id]
|
||||||
|
delete $scope.trackIds[track.id]
|
||||||
|
$scope.tracks.splice ($scope.tracks.indexOf track), 1
|
||||||
|
else
|
||||||
|
$scope.trackIds[track.id] = track
|
||||||
|
$scope.tracks.push track
|
||||||
|
|
||||||
|
$scope.isDirty = true
|
||||||
|
|
||||||
|
$scope.sortTracks = () ->
|
||||||
|
$scope.isDirty = true
|
||||||
|
|
||||||
$scope.touchModel = -> $scope.isDirty = true
|
$scope.touchModel = -> $scope.isDirty = true
|
||||||
|
|
||||||
|
$scope.setCover = (image, type) ->
|
||||||
|
delete $scope.album.cover_id
|
||||||
|
delete $scope.album.cover
|
||||||
|
|
||||||
|
if image == null
|
||||||
|
$scope.album.remove_cover = true
|
||||||
|
else if type == 'file'
|
||||||
|
$scope.album.cover = image
|
||||||
|
else if type == 'gallery'
|
||||||
|
$scope.album.cover_id = image.id
|
||||||
|
|
||||||
|
$scope.isDirty = true
|
||||||
|
|
||||||
$scope.refresh = () ->
|
$scope.refresh = () ->
|
||||||
return if $scope.isNew
|
return if $scope.isNew
|
||||||
$.getJSON('/api/web/albums/edit/' + $scope.data.selectedAlbum.id)
|
$.getJSON('/api/web/albums/edit/' + $state.params.album_id)
|
||||||
.done (album) -> $scope.$apply ->
|
.done (album) -> $scope.$apply ->
|
||||||
$scope.isDirty = false
|
$scope.isDirty = false
|
||||||
$scope.errors = {}
|
$scope.errors = {}
|
||||||
$scope.album =
|
$scope.album =
|
||||||
id: album.id
|
id: album.id
|
||||||
title: album.title
|
title: album.title
|
||||||
description: album.description
|
description: album.description
|
||||||
remove_cover: false
|
remove_cover: false
|
||||||
cover: album.cover_url
|
cover: album.cover_url
|
||||||
|
|
||||||
|
$scope.tracks = []
|
||||||
|
$scope.tracks.push track for track in album.tracks
|
||||||
|
$scope.trackIds[track.id] = track for track in album.tracks
|
||||||
|
$scope.data.selectedAlbum.title = album.title
|
||||||
|
$scope.data.selectedAlbum.description = album.description
|
||||||
|
$scope.data.selectedAlbum.covers.normal = album.real_cover_url
|
||||||
|
|
||||||
if $scope.isNew
|
if $scope.isNew
|
||||||
$scope.album =
|
$scope.album =
|
||||||
|
@ -33,16 +68,18 @@ angular.module('ponyfm').controller "account-albums-edit", [
|
||||||
$scope.$on '$destroy', -> $scope.data.isEditorOpen = false
|
$scope.$on '$destroy', -> $scope.data.isEditorOpen = false
|
||||||
|
|
||||||
$scope.saveAlbum = ->
|
$scope.saveAlbum = ->
|
||||||
|
return if !$scope.isNew && !$scope.isDirty
|
||||||
|
|
||||||
url =
|
url =
|
||||||
if $scope.isNew
|
if $scope.isNew
|
||||||
'/api/web/albums/create'
|
'/api/web/albums/create'
|
||||||
else
|
else
|
||||||
'/api/web/albums/edit' + $scope.album.id
|
'/api/web/albums/edit/' + $state.params.album_id
|
||||||
|
|
||||||
xhr = new XMLHttpRequest()
|
xhr = new XMLHttpRequest()
|
||||||
xhr.onload = -> $scope.$apply ->
|
xhr.onload = -> $scope.$apply ->
|
||||||
$scope.isSaving = false
|
$scope.isSaving = false
|
||||||
response = $.parseJSON(xhr.responseText).errors
|
response = $.parseJSON(xhr.responseText)
|
||||||
if xhr.status != 200
|
if xhr.status != 200
|
||||||
$scope.errors = {}
|
$scope.errors = {}
|
||||||
_.each response.errors, (value, key) -> $scope.errors[key] = value.join ', '
|
_.each response.errors, (value, key) -> $scope.errors[key] = value.join ', '
|
||||||
|
@ -51,6 +88,8 @@ angular.module('ponyfm').controller "account-albums-edit", [
|
||||||
$scope.$emit 'album-updated'
|
$scope.$emit 'album-updated'
|
||||||
|
|
||||||
if $scope.isNew
|
if $scope.isNew
|
||||||
|
$scope.isDirty = false
|
||||||
|
$scope.$emit 'album-created'
|
||||||
$state.transitionTo 'account-content.albums.edit', {album_id: response.id}
|
$state.transitionTo 'account-content.albums.edit', {album_id: response.id}
|
||||||
else
|
else
|
||||||
$scope.refresh()
|
$scope.refresh()
|
||||||
|
@ -65,12 +104,22 @@ angular.module('ponyfm').controller "account-albums-edit", [
|
||||||
else
|
else
|
||||||
formData.append name, value
|
formData.append name, value
|
||||||
|
|
||||||
|
formData.append 'track_ids', _.map($scope.tracks, (t) -> t.id).join()
|
||||||
|
|
||||||
xhr.open 'POST', url, true
|
xhr.open 'POST', url, true
|
||||||
xhr.setRequestHeader 'X-Token', pfm.token
|
xhr.setRequestHeader 'X-Token', pfm.token
|
||||||
$scope.isSaving = true
|
$scope.isSaving = true
|
||||||
xhr.send formData
|
xhr.send formData
|
||||||
|
|
||||||
$scope.deleteAlbum = ->
|
$scope.deleteAlbum = () ->
|
||||||
|
$dialog.messageBox('Delete ' + $scope.album.title, 'Are you sure you want to delete "' + $scope.album.title + '"? This cannot be undone.', [
|
||||||
|
{result: 'ok', label: 'Yes', cssClass: 'btn-danger'}, {result: 'cancel', label: 'No', cssClass: 'btn-primary'}
|
||||||
|
]).open().then (res) ->
|
||||||
|
return if res == 'cancel'
|
||||||
|
$.post('/api/web/albums/delete/' + $scope.album.id, {_token: window.pfm.token})
|
||||||
|
.then -> $scope.$apply ->
|
||||||
|
$scope.$emit 'album-deleted'
|
||||||
|
$state.transitionTo 'account-content.albums'
|
||||||
|
|
||||||
$scope.setCover = (image, type) ->
|
$scope.setCover = (image, type) ->
|
||||||
delete $scope.album.cover_id
|
delete $scope.album.cover_id
|
||||||
|
@ -84,4 +133,8 @@ angular.module('ponyfm').controller "account-albums-edit", [
|
||||||
$scope.album.cover_id = image.id
|
$scope.album.cover_id = image.id
|
||||||
|
|
||||||
$scope.isDirty = true
|
$scope.isDirty = true
|
||||||
|
|
||||||
|
$scope.$on '$stateChangeStart', (e) ->
|
||||||
|
return if $scope.selectedTrack == null || !$scope.isDirty
|
||||||
|
e.preventDefault() if !confirm('Are you sure you want to leave this page without saving your changes?')
|
||||||
]
|
]
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue