mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-21 20:48:00 +01:00
Merge pull request #105 from Poniverse/feature/replace_master_track_file
Feature/replace master track file
This commit is contained in:
commit
abdfe2f698
14 changed files with 491 additions and 91 deletions
|
@ -20,15 +20,15 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use AudioCache;
|
||||
use FFmpegMovie;
|
||||
use File;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Support\Str;
|
||||
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
|
||||
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\TrackFile;
|
||||
use AudioCache;
|
||||
use File;
|
||||
use Illuminate\Support\Str;
|
||||
use SplFileInfo;
|
||||
|
||||
/**
|
||||
|
@ -45,6 +45,9 @@ class GenerateTrackFilesCommand extends CommandBase
|
|||
private $track;
|
||||
private $autoPublish;
|
||||
private $sourceFile;
|
||||
private $isForUpload;
|
||||
private $isReplacingTrack;
|
||||
private $version;
|
||||
|
||||
protected static $_losslessFormats = [
|
||||
'flac',
|
||||
|
@ -53,11 +56,14 @@ class GenerateTrackFilesCommand extends CommandBase
|
|||
'alac'
|
||||
];
|
||||
|
||||
public function __construct(Track $track, SplFileInfo $sourceFile, bool $autoPublish = false)
|
||||
public function __construct(Track $track, SplFileInfo $sourceFile, bool $autoPublish = false, bool $isForUpload = false, bool $isReplacingTrack = false, int $version = 1)
|
||||
{
|
||||
$this->track = $track;
|
||||
$this->autoPublish = $autoPublish;
|
||||
$this->sourceFile = $sourceFile;
|
||||
$this->isForUpload = $isForUpload;
|
||||
$this->isReplacingTrack = $isReplacingTrack;
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,6 +104,7 @@ class GenerateTrackFilesCommand extends CommandBase
|
|||
$trackFile->is_master = true;
|
||||
$trackFile->format = $masterFormat;
|
||||
$trackFile->track_id = $this->track->id;
|
||||
$trackFile->version = $this->version;
|
||||
$trackFile->save();
|
||||
}
|
||||
|
||||
|
@ -126,13 +133,14 @@ class GenerateTrackFilesCommand extends CommandBase
|
|||
$trackFile->is_master = $name === 'FLAC' ? true : false;
|
||||
$trackFile->format = $name;
|
||||
$trackFile->status = TrackFile::STATUS_PROCESSING_PENDING;
|
||||
$trackFile->version = $this->version;
|
||||
|
||||
if (in_array($name, Track::$CacheableFormats) && !$trackFile->is_master) {
|
||||
$trackFile->is_cacheable = true;
|
||||
} else {
|
||||
$trackFile->is_cacheable = false;
|
||||
}
|
||||
$this->track->trackFiles()->save($trackFile);
|
||||
$this->track->trackFilesForAllVersions()->save($trackFile);
|
||||
|
||||
// All TrackFile records we need are synchronously created
|
||||
// before kicking off the encode jobs in order to avoid a race
|
||||
|
@ -142,16 +150,31 @@ class GenerateTrackFilesCommand extends CommandBase
|
|||
|
||||
try {
|
||||
foreach ($trackFiles as $trackFile) {
|
||||
$this->dispatch(new EncodeTrackFile($trackFile, false, true, $this->autoPublish));
|
||||
// Don't re-encode master files when replacing tracks with an already-uploaded version
|
||||
if ($trackFile->is_master && !$this->isForUpload && $this->isReplacingTrack) {
|
||||
continue;
|
||||
}
|
||||
$this->dispatch(new EncodeTrackFile($trackFile, false, false, $this->isForUpload, $this->isReplacingTrack));
|
||||
}
|
||||
|
||||
} catch (InvalidEncodeOptionsException $e) {
|
||||
$this->track->delete();
|
||||
// Only delete the track if the track is not being replaced
|
||||
if ($this->isReplacingTrack) {
|
||||
$this->track->version_upload_status = Track::STATUS_ERROR;
|
||||
$this->track->update();
|
||||
} else {
|
||||
$this->track->delete();
|
||||
}
|
||||
return CommandResponse::fail(['track' => [$e->getMessage()]]);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->track->delete();
|
||||
if ($this->isReplacingTrack) {
|
||||
$this->track->version_upload_status = Track::STATUS_ERROR;
|
||||
$this->track->update();
|
||||
} else {
|
||||
$this->track->delete();
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
|
@ -168,7 +191,8 @@ class GenerateTrackFilesCommand extends CommandBase
|
|||
* @param FFmpegMovie|string $file object or full path of the file we're checking
|
||||
* @return bool whether the given file is lossless
|
||||
*/
|
||||
private function isLosslessFile($file) {
|
||||
private function isLosslessFile($file)
|
||||
{
|
||||
if (is_string($file)) {
|
||||
$file = AudioCache::get($file);
|
||||
}
|
||||
|
@ -180,7 +204,8 @@ class GenerateTrackFilesCommand extends CommandBase
|
|||
* @param string $format
|
||||
* @return TrackFile|null
|
||||
*/
|
||||
private function trackFileExists(string $format) {
|
||||
return $this->track->trackFiles()->where('format', $format)->first();
|
||||
private function trackFileExists(string $format)
|
||||
{
|
||||
return $this->track->trackFilesForAllVersions()->where('format', $format)->where('version', $this->version)->first();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ use Gate;
|
|||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Input;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use AudioCache;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Validator;
|
||||
|
||||
|
@ -40,6 +39,21 @@ class UploadTrackCommand extends CommandBase
|
|||
private $_allowShortTrack;
|
||||
private $_customTrackSource;
|
||||
private $_autoPublishByDefault;
|
||||
private $_track;
|
||||
private $_version;
|
||||
private $_isReplacingTrack;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
if ($this->_isReplacingTrack) {
|
||||
return $this->_track && Gate::allows('edit', $this->_track);
|
||||
} else {
|
||||
return Gate::allows('create-track', $this->_artist);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* UploadTrackCommand constructor.
|
||||
|
@ -48,12 +62,16 @@ class UploadTrackCommand extends CommandBase
|
|||
* @param bool $allowShortTrack allow tracks shorter than 30 seconds
|
||||
* @param string|null $customTrackSource value to set in the track's "source" field; if left blank, "direct_upload" is used
|
||||
* @param bool $autoPublishByDefault
|
||||
* @param int $version
|
||||
* @param Track $track | null
|
||||
*/
|
||||
public function __construct(
|
||||
bool $allowLossy = false,
|
||||
bool $allowShortTrack = false,
|
||||
string $customTrackSource = null,
|
||||
bool $autoPublishByDefault = false
|
||||
bool $autoPublishByDefault = false,
|
||||
int $version = 1,
|
||||
$track = null
|
||||
) {
|
||||
$userSlug = Input::get('user_slug', null);
|
||||
$this->_artist =
|
||||
|
@ -65,14 +83,9 @@ class UploadTrackCommand extends CommandBase
|
|||
$this->_allowShortTrack = $allowShortTrack;
|
||||
$this->_customTrackSource = $customTrackSource;
|
||||
$this->_autoPublishByDefault = $autoPublishByDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return Gate::allows('create-track', $this->_artist);
|
||||
$this->_version = $version;
|
||||
$this->_track = $track;
|
||||
$this->_isReplacingTrack = $this->_track !== null && $version > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,22 +95,32 @@ class UploadTrackCommand extends CommandBase
|
|||
public function execute()
|
||||
{
|
||||
$trackFile = Input::file('track', null);
|
||||
$coverFile = Input::file('cover', null);
|
||||
if (!$this->_isReplacingTrack) {
|
||||
$coverFile = Input::file('cover', null);
|
||||
}
|
||||
|
||||
if (null === $trackFile) {
|
||||
if ($this->_isReplacingTrack) {
|
||||
$this->_track->version_upload_status = Track::STATUS_ERROR;
|
||||
$this->_track->update();
|
||||
}
|
||||
return CommandResponse::fail(['track' => ['You must upload an audio file!']]);
|
||||
}
|
||||
|
||||
$audio = \AudioCache::get($trackFile->getPathname());
|
||||
|
||||
$track = new Track();
|
||||
$track->user_id = $this->_artist->id;
|
||||
// The title set here is a placeholder; it'll be replaced by ParseTrackTagsCommand
|
||||
// if the file contains a title tag.
|
||||
$track->title = Input::get('title', pathinfo($trackFile->getClientOriginalName(), PATHINFO_FILENAME));
|
||||
$track->duration = $audio->getDuration();
|
||||
$track->save();
|
||||
$track->ensureDirectoryExists();
|
||||
if (!$this->_isReplacingTrack) {
|
||||
$this->_track = new Track();
|
||||
$this->_track->user_id = $this->_artist->id;
|
||||
// The title set here is a placeholder; it'll be replaced by ParseTrackTagsCommand
|
||||
// if the file contains a title tag.
|
||||
$this->_track->title = Input::get('title', pathinfo($trackFile->getClientOriginalName(), PATHINFO_FILENAME));
|
||||
// The duration/version of the track cannot be changed until the encoding is successful
|
||||
$this->_track->duration = $audio->getDuration();
|
||||
$this->_track->current_version = $this->_version;
|
||||
$this->_track->save();
|
||||
}
|
||||
$this->_track->ensureDirectoryExists();
|
||||
|
||||
if (!is_dir(Config::get('ponyfm.files_directory').'/tmp')) {
|
||||
mkdir(Config::get('ponyfm.files_directory').'/tmp', 0755, true);
|
||||
|
@ -106,13 +129,15 @@ class UploadTrackCommand extends CommandBase
|
|||
if (!is_dir(Config::get('ponyfm.files_directory').'/queued-tracks')) {
|
||||
mkdir(Config::get('ponyfm.files_directory').'/queued-tracks', 0755, true);
|
||||
}
|
||||
$trackFile = $trackFile->move(Config::get('ponyfm.files_directory').'/queued-tracks', $track->id);
|
||||
$trackFile = $trackFile->move(Config::get('ponyfm.files_directory').'/queued-tracks', $this->_track->id . 'v' . $this->_version);
|
||||
|
||||
$input = Input::all();
|
||||
$input['track'] = $trackFile;
|
||||
$input['cover'] = $coverFile;
|
||||
if (!$this->_isReplacingTrack) {
|
||||
$input['cover'] = $coverFile;
|
||||
}
|
||||
|
||||
$validator = \Validator::make($input, [
|
||||
$rules = [
|
||||
'track' =>
|
||||
'required|'
|
||||
. ($this->_allowLossy
|
||||
|
@ -120,44 +145,61 @@ class UploadTrackCommand extends CommandBase
|
|||
: 'audio_format:flac,alac,pcm,adpcm|')
|
||||
. ($this->_allowShortTrack ? '' : 'min_duration:30|')
|
||||
. 'audio_channels:1,2',
|
||||
|
||||
'auto_publish' => 'boolean',
|
||||
'title' => 'string',
|
||||
'track_type_id' => 'exists:track_types,id',
|
||||
'genre' => 'string',
|
||||
'album' => 'string',
|
||||
'track_number' => 'integer',
|
||||
'released_at' => 'date_format:'.Carbon::ISO8601,
|
||||
'description' => 'string',
|
||||
'lyrics' => 'string',
|
||||
'is_vocal' => 'boolean',
|
||||
'is_explicit' => 'boolean',
|
||||
'is_downloadable' => 'boolean',
|
||||
'is_listed' => 'boolean',
|
||||
'cover' => 'image|mimes:png,jpeg|min_width:350|min_height:350',
|
||||
'metadata' => 'json',
|
||||
]);
|
||||
];
|
||||
if (!$this->_isReplacingTrack) {
|
||||
array_push($rules, [
|
||||
'cover' => 'image|mimes:png,jpeg|min_width:350|min_height:350',
|
||||
'auto_publish' => 'boolean',
|
||||
'title' => 'string',
|
||||
'track_type_id' => 'exists:track_types,id',
|
||||
'genre' => 'string',
|
||||
'album' => 'string',
|
||||
'track_number' => 'integer',
|
||||
'released_at' => 'date_format:'.Carbon::ISO8601,
|
||||
'description' => 'string',
|
||||
'lyrics' => 'string',
|
||||
'is_vocal' => 'boolean',
|
||||
'is_explicit' => 'boolean',
|
||||
'is_downloadable' => 'boolean',
|
||||
'is_listed' => 'boolean',
|
||||
'metadata' => 'json'
|
||||
]);
|
||||
}
|
||||
$validator = \Validator::make($input, $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$track->delete();
|
||||
if ($this->_isReplacingTrack) {
|
||||
$this->_track->version_upload_status = Track::STATUS_ERROR;
|
||||
$this->_track->update();
|
||||
} else {
|
||||
$this->_track->delete();
|
||||
}
|
||||
return CommandResponse::fail($validator);
|
||||
}
|
||||
$autoPublish = (bool) ($input['auto_publish'] ?? $this->_autoPublishByDefault);
|
||||
$track->source = $this->_customTrackSource ?? 'direct_upload';
|
||||
|
||||
// If json_decode() isn't called here, Laravel will surround the JSON
|
||||
// string with quotes when storing it in the database, which breaks things.
|
||||
$track->metadata = json_decode(Input::get('metadata', null));
|
||||
$track->save();
|
||||
if (!$this->_isReplacingTrack) {
|
||||
// If json_decode() isn't called here, Laravel will surround the JSON
|
||||
// string with quotes when storing it in the database, which breaks things.
|
||||
$this->_track->metadata = json_decode(Input::get('metadata', null));
|
||||
}
|
||||
$autoPublish = (bool)($input['auto_publish'] ?? $this->_autoPublishByDefault);
|
||||
$this->_track->source = $this->_customTrackSource ?? 'direct_upload';
|
||||
$this->_track->save();
|
||||
|
||||
// Parse any tags in the uploaded files.
|
||||
$parseTagsCommand = new ParseTrackTagsCommand($track, $trackFile, $input);
|
||||
$result = $parseTagsCommand->execute();
|
||||
if ($result->didFail()) {
|
||||
return $result;
|
||||
if (!$this->_isReplacingTrack) {
|
||||
// Parse any tags in the uploaded files.
|
||||
$parseTagsCommand = new ParseTrackTagsCommand($this->_track, $trackFile, $input);
|
||||
$result = $parseTagsCommand->execute();
|
||||
if ($result->didFail()) {
|
||||
if ($this->_isReplacingTrack) {
|
||||
$this->_track->version_upload_status = Track::STATUS_ERROR;
|
||||
$this->_track->update();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
$generateTrackFiles = new GenerateTrackFilesCommand($track, $trackFile, $autoPublish);
|
||||
$generateTrackFiles = new GenerateTrackFilesCommand($this->_track, $trackFile, $autoPublish, true, $this->_isReplacingTrack, $this->_version);
|
||||
return $generateTrackFiles->execute();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ class RebuildTrack extends Command
|
|||
$track->restore();
|
||||
$this->info("Attempting to finish this track's upload...");
|
||||
|
||||
$sourceFile = new \SplFileInfo($track->getTemporarySourceFile());
|
||||
$sourceFile = new \SplFileInfo($track->getTemporarySourceFileForVersion($track->current_version));
|
||||
$generateTrackFiles = new GenerateTrackFilesCommand($track, $sourceFile, false);
|
||||
$result = $generateTrackFiles->execute();
|
||||
// The GenerateTrackFiles command will re-encode all TrackFiles.
|
||||
|
|
97
app/Console/Commands/VersionFiles.php
Normal file
97
app/Console/Commands/VersionFiles.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Kelvin Zhang
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Console\Commands;
|
||||
|
||||
use File;
|
||||
use Illuminate\Console\Command;
|
||||
use Poniverse\Ponyfm\Models\TrackFile;
|
||||
|
||||
class VersionFiles extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'version-files
|
||||
{--force : Skip all prompts.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Replaces track files of format [name].[ext] with [name]-v[version].[ext]';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('This will only version track files which exist on disk; non-existent files will be skipped.');
|
||||
|
||||
if ($this->option('force') || $this->confirm('Are you sure you want to rename all unversioned track files? [y|N]', false)) {
|
||||
|
||||
TrackFile::chunk(200, function ($trackFiles) {
|
||||
|
||||
$this->info('========== Start Chunk ==========');
|
||||
|
||||
foreach ($trackFiles as $trackFile) {
|
||||
/** @var TrackFile $trackFile */
|
||||
|
||||
// Check whether the unversioned file exists
|
||||
if (!File::exists($trackFile->getUnversionedFile())) {
|
||||
$this->info('ID ' . $trackFile->id . ' skipped - file not found');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Version the file and check the outcome
|
||||
if (File::move($trackFile->getUnversionedFile(), $trackFile->getFile())) {
|
||||
$this->info('ID ' . $trackFile->id . ' processed');
|
||||
} else {
|
||||
$this->error('ID ' . $trackFile->id . ' was unable to be renamed');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->info('=========== End Chunk ===========');
|
||||
|
||||
});
|
||||
|
||||
$this->info('Rebuild complete. Exiting.');
|
||||
|
||||
} else {
|
||||
$this->info('Rebuild cancelled. Exiting.');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -45,6 +45,7 @@ class Kernel extends ConsoleKernel
|
|||
\Poniverse\Ponyfm\Console\Commands\RebuildSearchIndex::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\MergeAccounts::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\FixMLPMAImages::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\VersionFiles::class
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,23 +20,24 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Auth;
|
||||
use File;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Input;
|
||||
use Poniverse\Ponyfm\Commands\DeleteTrackCommand;
|
||||
use Poniverse\Ponyfm\Commands\EditTrackCommand;
|
||||
use Poniverse\Ponyfm\Commands\GenerateTrackFilesCommand;
|
||||
use Poniverse\Ponyfm\Commands\UploadTrackCommand;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||
use Poniverse\Ponyfm\Models\Genre;
|
||||
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\Models\TrackFile;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\TrackType;
|
||||
use Poniverse\Ponyfm\Models\TrackTypes;
|
||||
use Auth;
|
||||
use Input;
|
||||
use Poniverse\Ponyfm\Models\TrackFile;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Response;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
class TracksController extends ApiControllerBase
|
||||
{
|
||||
|
@ -74,6 +75,75 @@ class TracksController extends ApiControllerBase
|
|||
return $this->execute(new EditTrackCommand($id, Input::all()));
|
||||
}
|
||||
|
||||
public function postUploadNewVersion($trackId)
|
||||
{
|
||||
session_write_close();
|
||||
|
||||
$track = Track::find($trackId);
|
||||
if (!$track) {
|
||||
return $this->notFound('Track not found!');
|
||||
}
|
||||
$this->authorize('edit', $track);
|
||||
|
||||
$track->version_upload_status = Track::STATUS_PROCESSING;
|
||||
$track->update();
|
||||
return $this->execute(new UploadTrackCommand(true, false, null, false, $track->getNextVersion(), $track));
|
||||
}
|
||||
|
||||
public function getVersionUploadStatus($trackId)
|
||||
{
|
||||
$track = Track::findOrFail($trackId);
|
||||
$this->authorize('edit', $track);
|
||||
|
||||
if ($track->version_upload_status === Track::STATUS_PROCESSING) {
|
||||
return Response::json(['message' => 'Processing...'], 202);
|
||||
|
||||
} elseif ($track->version_upload_status === Track::STATUS_COMPLETE) {
|
||||
return Response::json(['message' => 'Processing complete!'], 201);
|
||||
|
||||
} else {
|
||||
// something went wrong
|
||||
return Response::json(['error' => 'Processing failed!'], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function getVersionList($trackId)
|
||||
{
|
||||
$track = Track::findOrFail($trackId);
|
||||
$this->authorize('edit', $track);
|
||||
|
||||
$versions = [];
|
||||
$trackFiles = $track->trackFilesForAllVersions()->where('is_master', 'true')->get();
|
||||
foreach($trackFiles as $trackFile) {
|
||||
$versions[] = [
|
||||
'version' => $trackFile->version,
|
||||
'url' => '/tracks/' . $track->id . '/version-change/' . $trackFile->version,
|
||||
'created_at' => $trackFile->created_at->timestamp
|
||||
];
|
||||
}
|
||||
|
||||
return Response::json(['current_version' => $track->current_version, 'versions' => $versions], 200);
|
||||
}
|
||||
|
||||
public function getChangeVersion($trackId, $newVersion)
|
||||
{
|
||||
$track = Track::find($trackId);
|
||||
if (!$track) {
|
||||
return $this->notFound('Track not found!');
|
||||
}
|
||||
$this->authorize('edit', $track);
|
||||
|
||||
$masterTrackFile = $track->trackFilesForVersion($newVersion)->where('is_master', true)->first();
|
||||
if (!$masterTrackFile) {
|
||||
return $this->notFound('Version not found!');
|
||||
}
|
||||
|
||||
$track->version_upload_status = Track::STATUS_PROCESSING;
|
||||
$track->update();
|
||||
$sourceFile = new UploadedFile($masterTrackFile->getFile(), $masterTrackFile->getFilename());
|
||||
return $this->execute(new GenerateTrackFilesCommand($track, $sourceFile, false, false, true, $newVersion));
|
||||
}
|
||||
|
||||
public function getShow($id)
|
||||
{
|
||||
$track = Track::userDetails()->withComments()->find($id);
|
||||
|
|
|
@ -114,6 +114,11 @@ Route::group(['prefix' => 'api/web'], function() {
|
|||
Route::post('/tracks/delete/{id}', 'Api\Web\TracksController@postDelete');
|
||||
Route::post('/tracks/edit/{id}', 'Api\Web\TracksController@postEdit');
|
||||
|
||||
Route::post('/tracks/{id}/version-upload', 'Api\Web\TracksController@postUploadNewVersion');
|
||||
Route::get('/tracks/{id}/version-change/{version}', 'Api\Web\TracksController@getChangeVersion');
|
||||
Route::get('/tracks/{id}/version-upload-status', 'Api\Web\TracksController@getVersionUploadStatus');
|
||||
Route::get('/tracks/{id}/versions', 'Api\Web\TracksController@getVersionList');
|
||||
|
||||
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');
|
||||
|
|
|
@ -22,15 +22,15 @@
|
|||
namespace Poniverse\Ponyfm\Jobs;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Config;
|
||||
use DB;
|
||||
use File;
|
||||
use Config;
|
||||
use Log;
|
||||
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Bus\SelfHandling;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Log;
|
||||
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\TrackFile;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
|
@ -47,6 +47,10 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
* @var
|
||||
*/
|
||||
protected $isExpirable;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $autoPublishWhenComplete;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
@ -54,16 +58,17 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $autoPublishWhenComplete;
|
||||
protected $isReplacingTrack;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
* @param TrackFile $trackFile
|
||||
* @param bool $isExpirable
|
||||
* @param bool $isForUpload indicates whether this encode job is for an upload
|
||||
* @param bool $autoPublish
|
||||
* @param bool $isForUpload indicates whether this encode job is for an upload
|
||||
* @param bool $isReplacingTrack
|
||||
*/
|
||||
public function __construct(TrackFile $trackFile, $isExpirable, $isForUpload = false, $autoPublish = false)
|
||||
public function __construct(TrackFile $trackFile, $isExpirable, $autoPublish = false, $isForUpload = false, $isReplacingTrack = false)
|
||||
{
|
||||
if (
|
||||
(!$isForUpload && $trackFile->is_master) ||
|
||||
|
@ -74,8 +79,9 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
|
||||
$this->trackFile = $trackFile;
|
||||
$this->isExpirable = $isExpirable;
|
||||
$this->isForUpload = $isForUpload;
|
||||
$this->autoPublishWhenComplete = $autoPublish;
|
||||
$this->isForUpload = $isForUpload;
|
||||
$this->isReplacingTrack = $isReplacingTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,7 +98,7 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
Log::warning('Track file #'.$this->trackFile->id.' (track #'.$this->trackFile->track_id.') is already being processed!');
|
||||
return;
|
||||
|
||||
} elseif (!$this->trackFile->is_expired) {
|
||||
} elseif (!$this->trackFile->is_expired && File::exists($this->trackFile->getFile())) {
|
||||
Log::warning('Track file #'.$this->trackFile->id.' (track #'.$this->trackFile->track_id.') is still valid! No need to re-encode it.');
|
||||
return;
|
||||
}
|
||||
|
@ -103,11 +109,11 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
|
||||
// Use the track's master file as the source
|
||||
if ($this->isForUpload) {
|
||||
$source = $this->trackFile->track->getTemporarySourceFile();
|
||||
|
||||
$source = $this->trackFile->track->getTemporarySourceFileForVersion($this->trackFile->version);
|
||||
} else {
|
||||
$source = TrackFile::where('track_id', $this->trackFile->track_id)
|
||||
->where('is_master', true)
|
||||
->where('version', $this->trackFile->version)
|
||||
->first()
|
||||
->getFile();
|
||||
}
|
||||
|
@ -151,7 +157,7 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
$this->trackFile->status = TrackFile::STATUS_NOT_BEING_PROCESSED;
|
||||
$this->trackFile->save();
|
||||
|
||||
if ($this->isForUpload) {
|
||||
if ($this->isForUpload || $this->isReplacingTrack) {
|
||||
if (!$this->trackFile->is_master && $this->trackFile->is_cacheable) {
|
||||
File::delete($this->trackFile->getFile());
|
||||
}
|
||||
|
@ -166,7 +172,29 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
$this->trackFile->track->save();
|
||||
}
|
||||
|
||||
File::delete($this->trackFile->track->getTemporarySourceFile());
|
||||
if ($this->isReplacingTrack) {
|
||||
$oldVersion = $this->trackFile->track->current_version;
|
||||
|
||||
// Update the version of the track being uploaded
|
||||
$this->trackFile->track->duration = \AudioCache::get($source)->getDuration();
|
||||
$this->trackFile->track->current_version = $this->trackFile->version;
|
||||
$this->trackFile->track->version_upload_status = Track::STATUS_COMPLETE;
|
||||
$this->trackFile->track->update();
|
||||
|
||||
// Delete the non-master files for the old version
|
||||
if ($oldVersion !== $this->trackFile->version) {
|
||||
$trackFilesToDelete = $this->trackFile->track->trackFilesForVersion($oldVersion)->where('is_master', false)->get();
|
||||
foreach ($trackFilesToDelete as $trackFileToDelete) {
|
||||
if (File::exists($trackFileToDelete->getFile())) {
|
||||
File::delete($trackFileToDelete->getFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isForUpload) {
|
||||
File::delete($this->trackFile->track->getTemporarySourceFileForVersion($this->trackFile->version));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,5 +209,21 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
$this->trackFile->status = TrackFile::STATUS_PROCESSING_ERROR;
|
||||
$this->trackFile->expires_at = null;
|
||||
$this->trackFile->save();
|
||||
|
||||
if ($this->isReplacingTrack) {
|
||||
// If a new version is being uploaded to replace a file, yet the upload fails,
|
||||
// all track files for that version should be deleted as it would other clutter the version
|
||||
if ($this->isForUpload) {
|
||||
$trackFiles = $this->trackFile->track->trackFilesForVersion($this->trackFile->version)->get();
|
||||
foreach ($trackFiles as $trackFile) {
|
||||
if (File::exists($trackFile->getFile())) {
|
||||
File::delete($trackFile->getFile());
|
||||
}
|
||||
$trackFile->delete();
|
||||
}
|
||||
}
|
||||
$this->trackFile->track->version_upload_status = Track::STATUS_ERROR;
|
||||
$this->trackFile->track->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,7 +123,8 @@ class Album extends Model implements Searchable, Commentable, Favouritable
|
|||
}
|
||||
|
||||
public function trackFiles() {
|
||||
return $this->hasManyThrough(TrackFile::class, Track::class, 'album_id', 'track_id');
|
||||
$trackIds = $this->tracks->lists('id');
|
||||
return TrackFile::join('tracks', 'tracks.current_version', '=', 'track_files.version')->whereIn('track_id', $trackIds);
|
||||
}
|
||||
|
||||
public function comments():HasMany
|
||||
|
|
|
@ -226,7 +226,7 @@ class Playlist extends Model implements Searchable, Commentable, Favouritable
|
|||
public function trackFiles()
|
||||
{
|
||||
$trackIds = $this->tracks->lists('id');
|
||||
return TrackFile::whereIn('track_id', $trackIds);
|
||||
return TrackFile::join('tracks', 'tracks.current_version', '=', 'track_files.version')->whereIn('track_id', $trackIds);
|
||||
}
|
||||
|
||||
public function users()
|
||||
|
|
|
@ -536,10 +536,21 @@ class Track extends Model implements Searchable, Commentable, Favouritable
|
|||
}
|
||||
|
||||
public function trackFiles()
|
||||
{
|
||||
return $this->hasMany(TrackFile::class)->where('version', $this->current_version);
|
||||
}
|
||||
|
||||
|
||||
public function trackFilesForAllVersions()
|
||||
{
|
||||
return $this->hasMany(TrackFile::class);
|
||||
}
|
||||
|
||||
public function trackFilesForVersion(int $version)
|
||||
{
|
||||
return $this->hasMany(TrackFile::class)->where('track_files.version', $version);
|
||||
}
|
||||
|
||||
public function notifications()
|
||||
{
|
||||
return $this->morphMany(Activity::class, 'notification_type');
|
||||
|
@ -569,7 +580,7 @@ class Track extends Model implements Searchable, Commentable, Favouritable
|
|||
*/
|
||||
public function getFilesize($formatName)
|
||||
{
|
||||
$trackFile = $this->trackFiles->where('format', $formatName)->first();
|
||||
$trackFile = $this->trackFiles()->where('format', $formatName)->first();
|
||||
|
||||
if ($trackFile) {
|
||||
return (int) $trackFile->filesize;
|
||||
|
@ -637,7 +648,7 @@ class Track extends Model implements Searchable, Commentable, Favouritable
|
|||
|
||||
protected function getMasterTrackFile() : TrackFile
|
||||
{
|
||||
return $this->trackFiles->where('is_master', true)->first();
|
||||
return $this->trackFiles()->where('is_master', true)->first();
|
||||
}
|
||||
|
||||
public function getMasterFormatName() : string
|
||||
|
@ -688,7 +699,7 @@ class Track extends Model implements Searchable, Commentable, Favouritable
|
|||
|
||||
$format = self::$Formats[$format];
|
||||
|
||||
return "{$this->id}.{$format['extension']}";
|
||||
return "{$this->id}-v{$this->current_version}.{$format['extension']}";
|
||||
}
|
||||
|
||||
public function getDownloadFilenameFor($format)
|
||||
|
@ -715,7 +726,7 @@ class Track extends Model implements Searchable, Commentable, Favouritable
|
|||
|
||||
$format = self::$Formats[$format];
|
||||
|
||||
return "{$this->getDirectory()}/{$this->id}.{$format['extension']}";
|
||||
return "{$this->getDirectory()}/{$this->id}-v{$this->current_version}.{$format['extension']}";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -723,10 +734,11 @@ class Track extends Model implements Searchable, Commentable, Favouritable
|
|||
* This file is used during the upload process to generate the actual master
|
||||
* file stored by Pony.fm.
|
||||
*
|
||||
* @param int $version
|
||||
* @return string
|
||||
*/
|
||||
public function getTemporarySourceFile():string {
|
||||
return Config::get('ponyfm.files_directory').'/queued-tracks/'.$this->id;
|
||||
public function getTemporarySourceFileForVersion(int $version):string {
|
||||
return Config::get('ponyfm.files_directory').'/queued-tracks/'.$this->id.'v'.$version;
|
||||
}
|
||||
|
||||
|
||||
|
@ -914,4 +926,9 @@ class Track extends Model implements Searchable, Commentable, Favouritable
|
|||
public function getResourceType():string {
|
||||
return 'track';
|
||||
}
|
||||
|
||||
public function getNextVersion()
|
||||
{
|
||||
return $this->current_version + 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,11 @@ class TrackFile extends Model
|
|||
*/
|
||||
public static function findOrFailByExtension($trackId, $extension)
|
||||
{
|
||||
$track = Track::find($trackId);
|
||||
if (!$track) {
|
||||
App::abort(404);
|
||||
}
|
||||
|
||||
// find the extension's format
|
||||
$requestedFormatName = null;
|
||||
foreach (Track::$Formats as $name => $format) {
|
||||
|
@ -96,6 +101,7 @@ class TrackFile extends Model
|
|||
with('track')
|
||||
->where('track_id', $trackId)
|
||||
->where('format', $requestedFormatName)
|
||||
->where('version', $track->current_version)
|
||||
->first();
|
||||
|
||||
if ($trackFile === null) {
|
||||
|
@ -148,11 +154,21 @@ class TrackFile extends Model
|
|||
}
|
||||
|
||||
public function getFile()
|
||||
{
|
||||
return "{$this->getDirectory()}/{$this->track_id}-v{$this->version}.{$this->extension}";
|
||||
}
|
||||
|
||||
public function getUnversionedFile()
|
||||
{
|
||||
return "{$this->getDirectory()}/{$this->track_id}.{$this->extension}";
|
||||
}
|
||||
|
||||
public function getFilename()
|
||||
{
|
||||
return "{$this->track_id}-v{$this->track->current_version}.{$this->extension}";
|
||||
}
|
||||
|
||||
public function getUnversionedFilename()
|
||||
{
|
||||
return "{$this->track_id}.{$this->extension}";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Kelvin Zhang
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddVersionColumnToTrackFilesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('track_files', function (Blueprint $table) {
|
||||
$table->integer('version')->unsigned()->default(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('track_files', function (Blueprint $table) {
|
||||
$table->dropColumn('version');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddVersionColumnToTracksTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('tracks', function (Blueprint $table) {
|
||||
$table->integer('current_version')->unsigned()->default(1);
|
||||
$table->integer('version_upload_status')->unsigned()->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('tracks', function (Blueprint $table) {
|
||||
$table->dropColumn('current_version');
|
||||
$table->dropColumn('version_upload_status');
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue