mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-22 21:18:00 +01:00
T357: Separated track publishing and classification into its own script, fixed an issue with reading comments from ID3 tags, and added lossy support to UploadTrackCommand.
This commit is contained in:
parent
dbab3a9ecc
commit
f17e824586
7 changed files with 412 additions and 161 deletions
210
app/commands/ClassifyMLPMA.php
Normal file
210
app/commands/ClassifyMLPMA.php
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Entities\ShowSong;
|
||||||
|
use Entities\Track;
|
||||||
|
use Entities\TrackType;
|
||||||
|
|
||||||
|
class ClassifyMLPMA extends Command {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command name.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name = 'mlpma:classify';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Add Pony.fm-specific metadata to imported MLPMA tracks.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function fire()
|
||||||
|
{
|
||||||
|
// Get the list of tracks that need classification
|
||||||
|
$tracks = DB::table('mlpma_tracks')
|
||||||
|
->orderBy('id')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach ($tracks as $track) {
|
||||||
|
$parsedTags = json_decode($track->parsed_tags);
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Original, show song remix, fan song remix, show audio remix, or ponified song?
|
||||||
|
//==========================================================================================================
|
||||||
|
$trackType = TrackType::ORIGINAL_TRACK;
|
||||||
|
$linkedSongIds = [];
|
||||||
|
|
||||||
|
$sanitizedTrackTitle = $parsedTags['title'];
|
||||||
|
$sanitizedTrackTitle = str_replace([' - ', 'ft.', '*'], ' ', $sanitizedTrackTitle);
|
||||||
|
|
||||||
|
$queriedTitle = DB::connection()->getPdo()->quote($sanitizedTrackTitle);
|
||||||
|
$officialSongs = ShowSong::select(['id', 'title'])
|
||||||
|
->whereRaw("
|
||||||
|
MATCH (title)
|
||||||
|
AGAINST ($queriedTitle IN BOOLEAN MODE)
|
||||||
|
")
|
||||||
|
->get();
|
||||||
|
|
||||||
|
|
||||||
|
// If it has "Ingram" in the name, it's definitely an official song remix.
|
||||||
|
if (Str::contains(Str::lower($track->filename), 'ingram')) {
|
||||||
|
$this->comment('This is an official song remix!');
|
||||||
|
|
||||||
|
list($trackType, $linkedSongIds) = $this->classifyTrack($track->filename, $officialSongs, true);
|
||||||
|
|
||||||
|
|
||||||
|
// If it has "remix" in the name, it's definitely a remix.
|
||||||
|
} else if (Str::contains(Str::lower($sanitizedTrackTitle), 'remix')) {
|
||||||
|
$this->comment('This is some kind of remix!');
|
||||||
|
|
||||||
|
list($trackType, $linkedSongIds) = $this->classifyTrack($track->filename, $officialSongs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Attach the data and publish the track!
|
||||||
|
//==========================================================================================================
|
||||||
|
|
||||||
|
$track = Track::find($track->track_id);
|
||||||
|
|
||||||
|
$track->track_type_id = $trackType;
|
||||||
|
$track->published_at = $parsedTags['released_at'];
|
||||||
|
$track->save();
|
||||||
|
|
||||||
|
if (sizeof($linkedSongIds) > 0) {
|
||||||
|
$track->showSongs()->attach($linkedSongIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the console command arguments.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getArguments()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('example', InputArgument::REQUIRED, 'An example argument.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the console command options.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getOptions()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('example', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines what type of track the given file is. If unable to guess, the user
|
||||||
|
* is asked to identify it interactively.
|
||||||
|
*
|
||||||
|
* @param string $filename
|
||||||
|
* @param \Entities\ShowSong[] $officialSongs
|
||||||
|
* @param bool|false $isRemixOfOfficialTrack
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function classifyTrack($filename, $officialSongs, $isRemixOfOfficialTrack = false) {
|
||||||
|
$trackTypeId = null;
|
||||||
|
$linkedSongIds = [];
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($officialSongs as $song) {
|
||||||
|
$this->comment('=> Matched official song: [' . $song->id . '] ' . $song->title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isRemixOfOfficialTrack && sizeof($officialSongs) === 1) {
|
||||||
|
$linkedSongIds = [$officialSongs[0]->id];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if ($isRemixOfOfficialTrack) {
|
||||||
|
$this->question('Multiple official songs matched! Please enter the ID of the correct one.');
|
||||||
|
|
||||||
|
} else if (sizeof($officialSongs) > 0) {
|
||||||
|
$this->question('This looks like a remix of an official song!');
|
||||||
|
$this->question('Press "r" if the match above is right!');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->question('Exactly what kind of track is this?');
|
||||||
|
|
||||||
|
}
|
||||||
|
$this->question('If this is a medley, multiple song ID\'s can be separated by commas. ');
|
||||||
|
$this->question(' ');
|
||||||
|
$this->question(' ' . $filename . ' ');
|
||||||
|
$this->question(' ');
|
||||||
|
$this->question(' r = official song remix (accept all "guessed" matches) ');
|
||||||
|
$this->question(' # = official song remix (enter the ID(s) of the show song(s)) ');
|
||||||
|
$this->question(' a = show audio remix ');
|
||||||
|
$this->question(' f = fan track remix ');
|
||||||
|
$this->question(' p = ponified track ');
|
||||||
|
$this->question(' o = original track ');
|
||||||
|
$this->question(' ');
|
||||||
|
$input = $this->ask('[r/#/a/f/p/o]: ');
|
||||||
|
|
||||||
|
switch ($input) {
|
||||||
|
case 'r':
|
||||||
|
$trackTypeId = TrackType::OFFICIAL_TRACK_REMIX;
|
||||||
|
foreach ($officialSongs as $officialSong) {
|
||||||
|
$linkedSongIds[] = (int) $officialSong->id;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'a':
|
||||||
|
$trackTypeId = TrackType::OFFICIAL_AUDIO_REMIX;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
$trackTypeId = TrackType::FAN_TRACK_REMIX;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
$trackTypeId = TrackType::PONIFIED_TRACK;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
$trackTypeId = TrackType::ORIGINAL_TRACK;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$trackTypeId = TrackType::OFFICIAL_TRACK_REMIX;
|
||||||
|
$linkedSongIds = explode(',', $input);
|
||||||
|
$linkedSongIds = array_map(function ($item) {
|
||||||
|
return (int) $item;
|
||||||
|
}, $linkedSongIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$trackTypeId, $linkedSongIds];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ use Entities\User;
|
||||||
use Entities\ShowSong;
|
use Entities\ShowSong;
|
||||||
use Entities\Track;
|
use Entities\Track;
|
||||||
use Entities\TrackType;
|
use Entities\TrackType;
|
||||||
|
use Commands\UploadTrackCommand;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ class ImportMLPMA extends Command {
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $name = 'import-mlpma';
|
protected $name = 'mlpma:import';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
|
@ -86,18 +87,36 @@ class ImportMLPMA extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Has this track already been imported?
|
||||||
|
$importedTrack = DB::table('mlpma_tracks')
|
||||||
|
->where('filename', '=', $file->getFilename())
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($importedTrack) {
|
||||||
|
$this->comment('This track has already been imported! Skipping...' . PHP_EOL);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================================================
|
//==========================================================================================================
|
||||||
// Extract the original tags.
|
// Extract the original tags.
|
||||||
//==========================================================================================================
|
//==========================================================================================================
|
||||||
$getId3 = new getID3;
|
$getId3 = new getID3;
|
||||||
$tags = $getId3->analyze($file->getPathname());
|
|
||||||
|
|
||||||
|
// all tags read by getID3, including the cover art
|
||||||
|
$allTags = $getId3->analyze($file->getPathname());
|
||||||
|
|
||||||
|
// tags specific to a file format (ID3 or Atom), pre-normalization but with cover art removed
|
||||||
|
$rawTags = [];
|
||||||
|
|
||||||
|
// normalized tags used by Pony.fm
|
||||||
$parsedTags = [];
|
$parsedTags = [];
|
||||||
|
|
||||||
if ($file->getExtension() === 'mp3') {
|
if ($file->getExtension() === 'mp3') {
|
||||||
$parsedTags = $this->getId3Tags($tags);
|
list($parsedTags, $rawTags) = $this->getId3Tags($allTags);
|
||||||
|
|
||||||
} else if ($file->getExtension() === 'm4a') {
|
} else if ($file->getExtension() === 'm4a') {
|
||||||
$parsedTags = $this->getAtomTags($tags);
|
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,6 +142,9 @@ class ImportMLPMA extends Command {
|
||||||
$releasedAt = $modifiedDate;
|
$releasedAt = $modifiedDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is later used by the classification/publishing script to determine the publication date.
|
||||||
|
$parsedTags['released_at'] = $releasedAt;
|
||||||
|
|
||||||
//==========================================================================================================
|
//==========================================================================================================
|
||||||
// Does this track have vocals?
|
// Does this track have vocals?
|
||||||
//==========================================================================================================
|
//==========================================================================================================
|
||||||
|
@ -163,8 +185,8 @@ class ImportMLPMA extends Command {
|
||||||
// Extract the cover art, if any exists.
|
// Extract the cover art, if any exists.
|
||||||
//==========================================================================================================
|
//==========================================================================================================
|
||||||
$coverId = null;
|
$coverId = null;
|
||||||
if (array_key_exists('comments', $tags) && array_key_exists('picture', $tags['comments'])) {
|
if (array_key_exists('comments', $allTags) && array_key_exists('picture', $allTags['comments'])) {
|
||||||
$image = $tags['comments']['picture'][0];
|
$image = $allTags['comments']['picture'][0];
|
||||||
|
|
||||||
if ($image['image_mime'] === 'image/png') {
|
if ($image['image_mime'] === 'image/png') {
|
||||||
$extension = 'png';
|
$extension = 'png';
|
||||||
|
@ -220,153 +242,58 @@ class ImportMLPMA extends Command {
|
||||||
$albumId = $album->id;
|
$albumId = $album->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Original, show song remix, fan song remix, show audio remix, or ponified song?
|
|
||||||
//==========================================================================================================
|
|
||||||
$trackType = TrackType::ORIGINAL_TRACK;
|
|
||||||
$linkedSongIds = [];
|
|
||||||
|
|
||||||
$sanitizedTrackTitle = $parsedTags['title'];
|
|
||||||
$sanitizedTrackTitle = str_replace(' - ', ' ', $sanitizedTrackTitle);
|
|
||||||
$sanitizedTrackTitle = str_replace('ft. ', '', $sanitizedTrackTitle);
|
|
||||||
$sanitizedTrackTitle = str_replace('*', '', $sanitizedTrackTitle);
|
|
||||||
|
|
||||||
$queriedTitle = DB::connection()->getPdo()->quote($sanitizedTrackTitle);
|
|
||||||
$officialSongs = ShowSong::select(['id', 'title'])
|
|
||||||
->whereRaw("
|
|
||||||
MATCH (title)
|
|
||||||
AGAINST ($queriedTitle IN BOOLEAN MODE)
|
|
||||||
")
|
|
||||||
->get();
|
|
||||||
|
|
||||||
|
|
||||||
// If it has "Ingram" in the name, it's definitely an official song remix.
|
|
||||||
if (Str::contains(Str::lower($file->getFilename()), 'ingram')) {
|
|
||||||
$this->comment('This is an official song remix!');
|
|
||||||
|
|
||||||
list($trackType, $linkedSongIds) = $this->classifyTrack($file, $officialSongs, true);
|
|
||||||
|
|
||||||
|
|
||||||
// If it has "remix" in the name, it's definitely a remix.
|
|
||||||
} else if (Str::contains(Str::lower($sanitizedTrackTitle), 'remix')) {
|
|
||||||
$this->comment('This is some kind of remix!');
|
|
||||||
|
|
||||||
list($trackType, $linkedSongIds) = $this->classifyTrack($file, $officialSongs);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================================================
|
//==========================================================================================================
|
||||||
// Save this track.
|
// Save this track.
|
||||||
//==========================================================================================================
|
//==========================================================================================================
|
||||||
$title = $parsedTags['title'];
|
$title = $parsedTags['title'];
|
||||||
|
//
|
||||||
|
// $track = Track::where('user_id', '=', $artist->id)
|
||||||
|
// ->where('title', '=', $title)
|
||||||
|
// ->first();
|
||||||
|
|
||||||
// Has this track already been imported?
|
// "upload" the track to Pony.fm
|
||||||
$track = Track::where('user_id', '=', $artist->id)
|
Auth::loginUsingId($artist->id);
|
||||||
->where('title', '=', $title)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if (!$track) {
|
$trackFile = new UploadedFile($file->getPathname(), $file->getFilename(), $allTags['mime_type']);
|
||||||
$track = new Track;
|
Input::instance()->files->add(['track' => $trackFile]);
|
||||||
|
|
||||||
|
$upload = new UploadTrackCommand(true);
|
||||||
|
$result = $upload->execute();
|
||||||
|
|
||||||
|
// var_dump(null);
|
||||||
|
|
||||||
|
if ($result->didFail()) {
|
||||||
|
$this->error(json_encode($result->getValidator()->messages()->getMessages(), JSON_PRETTY_PRINT));
|
||||||
|
} else {
|
||||||
|
DB::table('mlpma_tracks')
|
||||||
|
->insert([
|
||||||
|
'track_id' => $result['id'],
|
||||||
|
'path' => $file->getRelativePath(),
|
||||||
|
'filename' => $file->getFilename(),
|
||||||
|
'extension' => $file->getExtension(),
|
||||||
|
'imported_at' => Carbon::now(),
|
||||||
|
'parsed_tags' => json_encode($parsedTags),
|
||||||
|
'raw_tags' => json_encode($rawTags),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$track = Track::find($result['id']);
|
||||||
|
var_dump($track);
|
||||||
|
|
||||||
$track->user_id = $artist->id;
|
|
||||||
$track->title = $parsedTags['title'];
|
$track->title = $parsedTags['title'];
|
||||||
$track->cover_id = $coverId;
|
$track->cover_id = $coverId;
|
||||||
$track->album_id = $albumId;
|
$track->album_id = $albumId;
|
||||||
|
$track->track_number = $parsedTags['track_number'];
|
||||||
$track->released_at = $releasedAt;
|
$track->released_at = $releasedAt;
|
||||||
$track->is_vocal = $isVocal;
|
$track->is_vocal = $isVocal;
|
||||||
$track->track_type_id = $trackType;
|
|
||||||
|
|
||||||
$track->save();
|
$track->save();
|
||||||
|
|
||||||
if (sizeof($linkedSongIds) > 0) {
|
|
||||||
$track->showSongs()->attach($linkedSongIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: mark imported tracks as needing QA
|
|
||||||
} else {
|
|
||||||
$this->comment('This track has already been imported!');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
echo PHP_EOL.PHP_EOL;
|
echo PHP_EOL.PHP_EOL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function classifyTrack($file, $officialSongs, $isRemixOfOfficialTrack = false)
|
|
||||||
{
|
|
||||||
$trackTypeId = null;
|
|
||||||
$linkedSongIds = [];
|
|
||||||
|
|
||||||
|
|
||||||
foreach ($officialSongs as $song) {
|
|
||||||
$this->comment('=> Matched official song: [' . $song->id . '] ' . $song->title);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($isRemixOfOfficialTrack && sizeof($officialSongs) === 1) {
|
|
||||||
$linkedSongIds = [$officialSongs[0]->id];
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if ($isRemixOfOfficialTrack) {
|
|
||||||
$this->question('Multiple official songs matched! Please enter the ID of the correct one.');
|
|
||||||
|
|
||||||
} else if (sizeof($officialSongs) > 0) {
|
|
||||||
$this->question('This looks like a remix of an official song!');
|
|
||||||
$this->question('Press "r" if the match above is right!');
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$this->question('Exactly what kind of track is this?');
|
|
||||||
|
|
||||||
}
|
|
||||||
$this->question('If this is a medley, multiple song ID\'s can be separated by commas. ');
|
|
||||||
$this->question(' ');
|
|
||||||
$this->question(' '.$file->getFilename().' ');
|
|
||||||
$this->question(' ');
|
|
||||||
$this->question(' r = official song remix (accept all "guessed" matches) ');
|
|
||||||
$this->question(' # = official song remix (enter the ID(s) of the show song(s)) ');
|
|
||||||
$this->question(' a = show audio remix ');
|
|
||||||
$this->question(' f = fan track remix ');
|
|
||||||
$this->question(' p = ponified track ');
|
|
||||||
$this->question(' o = original track ');
|
|
||||||
$this->question(' ');
|
|
||||||
$input = $this->ask('[r/#/a/f/p/o]: ');
|
|
||||||
|
|
||||||
switch ($input) {
|
|
||||||
case 'r':
|
|
||||||
$trackTypeId = TrackType::OFFICIAL_TRACK_REMIX;
|
|
||||||
foreach ($officialSongs as $officialSong) {
|
|
||||||
$linkedSongIds[] = (int) $officialSong->id;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'a':
|
|
||||||
$trackTypeId = TrackType::OFFICIAL_AUDIO_REMIX;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'f':
|
|
||||||
$trackTypeId = TrackType::FAN_TRACK_REMIX;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'p':
|
|
||||||
$trackTypeId = TrackType::PONIFIED_TRACK;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'o':
|
|
||||||
$trackTypeId = TrackType::ORIGINAL_TRACK;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
$trackTypeId = TrackType::OFFICIAL_TRACK_REMIX;
|
|
||||||
$linkedSongIds = explode(',', $input);
|
|
||||||
$linkedSongIds = array_map(function ($item) {
|
|
||||||
return (int) $item;
|
|
||||||
}, $linkedSongIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [$trackTypeId, $linkedSongIds];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the console command arguments.
|
* Get the console command arguments.
|
||||||
*
|
*
|
||||||
|
@ -394,8 +321,23 @@ class ImportMLPMA extends Command {
|
||||||
*/
|
*/
|
||||||
protected function getId3Tags($rawTags) {
|
protected function getId3Tags($rawTags) {
|
||||||
$tags = $rawTags['tags']['id3v2'];
|
$tags = $rawTags['tags']['id3v2'];
|
||||||
|
$comment = null;
|
||||||
|
|
||||||
|
if (isset($tags['comment'])) {
|
||||||
|
// The "comment" tag comes in with a badly encoded string index
|
||||||
|
// so its array key has to be used implicitly.
|
||||||
|
$key = array_keys($tags['comment'])[0];
|
||||||
|
|
||||||
|
// The comment may have a null byte at the end. trim() removes it.
|
||||||
|
$comment = trim($tags['comment'][$key]);
|
||||||
|
|
||||||
|
// Replace the malformed comment with the "fixed" one.
|
||||||
|
unset($tags['comment'][$key]);
|
||||||
|
$tags['comment'][0] = $comment;
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
[
|
||||||
'title' => $tags['title'][0],
|
'title' => $tags['title'][0],
|
||||||
'artist' => $tags['artist'][0],
|
'artist' => $tags['artist'][0],
|
||||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||||
|
@ -403,9 +345,10 @@ class ImportMLPMA extends Command {
|
||||||
'track_number' => isset($tags['track_number']) ? $tags['track_number'][0] : null,
|
'track_number' => isset($tags['track_number']) ? $tags['track_number'][0] : null,
|
||||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||||
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
||||||
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
'comments' => $comment,
|
||||||
'lyrics' => isset($tags['unsynchronised_lyric']) ? $tags['unsynchronised_lyric'][0] : null,
|
'lyrics' => isset($tags['unsynchronised_lyric']) ? $tags['unsynchronised_lyric'][0] : null,
|
||||||
];
|
],
|
||||||
|
$tags];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -422,6 +365,7 @@ class ImportMLPMA extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
[
|
||||||
'title' => $tags['title'][0],
|
'title' => $tags['title'][0],
|
||||||
'artist' => $tags['artist'][0],
|
'artist' => $tags['artist'][0],
|
||||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||||
|
@ -432,7 +376,8 @@ class ImportMLPMA extends Command {
|
||||||
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
||||||
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
||||||
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
||||||
];
|
],
|
||||||
|
$tags];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class CreateMlpmaTable extends Migration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('mlpma_tracks', function(\Illuminate\Database\Schema\Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->integer('track_id')->unsigned()->index();
|
||||||
|
$table->string('path')->index();
|
||||||
|
$table->string('filename')->index();
|
||||||
|
$table->string('extension')->index();
|
||||||
|
$table->dateTime('imported_at');
|
||||||
|
$table->longText('parsed_tags');
|
||||||
|
$table->longText('raw_tags');
|
||||||
|
|
||||||
|
$table->foreign('track_id')->references('id')->on('tracks');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::drop('mlpma_tracks');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,6 +25,7 @@
|
||||||
// value is the file array itself
|
// value is the file array itself
|
||||||
// parameters is a list of formats the file can be, verified via ffmpeg
|
// parameters is a list of formats the file can be, verified via ffmpeg
|
||||||
$file = AudioCache::get($value->getPathname());
|
$file = AudioCache::get($value->getPathname());
|
||||||
|
var_dump($file->getAudioCodec());
|
||||||
return in_array($file->getAudioCodec(), $parameters);
|
return in_array($file->getAudioCodec(), $parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,28 @@
|
||||||
|
|
||||||
use Entities\Track;
|
use Entities\Track;
|
||||||
use Entities\TrackFile;
|
use Entities\TrackFile;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use AudioCache;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class UploadTrackCommand extends CommandBase {
|
class UploadTrackCommand extends CommandBase {
|
||||||
|
private $_allowLossy;
|
||||||
|
private $_losslessFormats = [
|
||||||
|
'flac',
|
||||||
|
'pcm_s16le ([1][0][0][0] / 0x0001)',
|
||||||
|
'pcm_s16be',
|
||||||
|
'adpcm_ms ([2][0][0][0] / 0x0002)',
|
||||||
|
'pcm_s24le ([1][0][0][0] / 0x0001)',
|
||||||
|
'pcm_s24be',
|
||||||
|
'pcm_f32le ([3][0][0][0] / 0x0003)',
|
||||||
|
'pcm_f32be (fl32 / 0x32336C66)'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct($allowLossy = false) {
|
||||||
|
$this->_allowLossy = $allowLossy;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
|
@ -26,7 +45,7 @@
|
||||||
$validator = \Validator::make(['track' => $trackFile], [
|
$validator = \Validator::make(['track' => $trackFile], [
|
||||||
'track' =>
|
'track' =>
|
||||||
'required|'
|
'required|'
|
||||||
. 'audio_format:flac,pcm_s16le ([1][0][0][0] / 0x0001),pcm_s16be,adpcm_ms ([2][0][0][0] / 0x0002),pcm_s24le ([1][0][0][0] / 0x0001),pcm_s24be,pcm_f32le ([3][0][0][0] / 0x0003),pcm_f32be (fl32 / 0x32336C66)|'
|
. $this->_allowLossy ? '' : 'audio_format:'.implode(',', $this->_losslessFormats).'|'
|
||||||
. 'audio_channels:1,2|'
|
. 'audio_channels:1,2|'
|
||||||
. 'sample_rate:44100,48000,88200,96000,176400,192000|'
|
. 'sample_rate:44100,48000,88200,96000,176400,192000|'
|
||||||
. 'min_duration:30'
|
. 'min_duration:30'
|
||||||
|
@ -53,7 +72,40 @@
|
||||||
|
|
||||||
$processes = [];
|
$processes = [];
|
||||||
|
|
||||||
|
// Lossy uploads need to be identified and set as the master file
|
||||||
|
// without being re-encoded.
|
||||||
|
$audioObject = AudioCache::get($source);
|
||||||
|
$isLossyUpload = !in_array($audioObject->getAudioCodec(), $this->_losslessFormats);
|
||||||
|
|
||||||
|
if ($isLossyUpload) {
|
||||||
|
|
||||||
|
if ($audioObject->getAudioCodec() === 'mp3') {
|
||||||
|
$masterFormat = 'MP3';
|
||||||
|
|
||||||
|
} else if (Str::startsWith($audioObject->getAudioCodec(), 'aac')) {
|
||||||
|
$masterFormat = 'AAC';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$validator->messages()->add('track', 'The track does not contain audio in a known lossy format.');
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$trackFile = new TrackFile();
|
||||||
|
$trackFile->is_master = true;
|
||||||
|
$trackFile->format = $masterFormat;
|
||||||
|
|
||||||
|
// Lossy masters are copied into the datastore - no re-encoding involved.
|
||||||
|
File::copy($source, $trackFile->getFilename());
|
||||||
|
$track->trackFiles()->save($trackFile);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Track::$Formats as $name => $format) {
|
foreach (Track::$Formats as $name => $format) {
|
||||||
|
// Don't bother with lossless transcodes of lossy uploads, and
|
||||||
|
// don't re-encode the lossy master.
|
||||||
|
if ($isLossyUpload && ($format['is_lossless'] || $name === $masterFormat)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$trackFile = new TrackFile();
|
$trackFile = new TrackFile();
|
||||||
$trackFile->is_master = $name === 'FLAC' ? true : false;
|
$trackFile->is_master = $name === 'FLAC' ? true : false;
|
||||||
$trackFile->format = $name;
|
$trackFile->format = $name;
|
||||||
|
|
|
@ -24,11 +24,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
public static $Formats = [
|
public static $Formats = [
|
||||||
'FLAC' => ['index' => 0, '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}'],
|
'FLAC' => ['index' => 0, 'is_lossless' => true, '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' => ['index' => 1, '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}'],
|
'MP3' => ['index' => 1, 'is_lossless' => false, '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' => ['index' => 2, '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}'],
|
'OGG Vorbis' => ['index' => 2, 'is_lossless' => false, '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' => ['index' => 3, '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}'],
|
'AAC' => ['index' => 3, 'is_lossless' => false, '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' => ['index' => 4, 'extension' => 'alac.m4a', 'tag_format' => 'AtomicParsley', 'tag_method' => 'updateTagsWithAtomicParsley', 'mime_type' => 'audio/mp4', 'command' => 'ffmpeg 2>&1 -y -i {$source} -acodec alac {$target}'],
|
'ALAC' => ['index' => 4, 'is_lossless' => true, '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() {
|
||||||
|
@ -430,7 +430,11 @@
|
||||||
|
|
||||||
public function updateTags() {
|
public function updateTags() {
|
||||||
$this->trackFiles()->touch();
|
$this->trackFiles()->touch();
|
||||||
foreach (self::$Formats as $format => $data) {
|
|
||||||
|
foreach ($this->trackFiles as $trackFile) {
|
||||||
|
$format = $trackFile->format;
|
||||||
|
$data = self::$Formats[$format];
|
||||||
|
|
||||||
$this->{$data['tag_method']}($format);
|
$this->{$data['tag_method']}($format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,3 +14,4 @@
|
||||||
Artisan::add(new MigrateOldData);
|
Artisan::add(new MigrateOldData);
|
||||||
Artisan::add(new RefreshCache);
|
Artisan::add(new RefreshCache);
|
||||||
Artisan::add(new ImportMLPMA);
|
Artisan::add(new ImportMLPMA);
|
||||||
|
Artisan::add(new ClassifyMLPMA);
|
||||||
|
|
Loading…
Reference in a new issue