mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2025-02-18 02:54:21 +01:00
Merge branch 'master' into feature/track-removals
This commit is contained in:
commit
e63c18f6be
311 changed files with 6611 additions and 23058 deletions
19
README.md
19
README.md
|
@ -33,13 +33,14 @@ these are smaller in scope and easier to tackle if you're unfamiliar with the co
|
||||||
Starting a dev environment
|
Starting a dev environment
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
To begin development, you must do three things:
|
To begin development, do the following:
|
||||||
|
|
||||||
1. Install the `vagrant-hostmanager` plugin: `vagrant plugin install vagrant-hostmanager`
|
1. Install [Vagrant](https://www.vagrantup.com/downloads.html) and
|
||||||
|
[VirtualBox](https://www.virtualbox.org/wiki/Downloads) if you don't have them already.
|
||||||
|
|
||||||
2. Install the `vagrant-bindfs` plugin: `vagrant plugin install vagrant-bindfs`
|
2. Install the `vagrant-hostmanager` plugin: `vagrant plugin install vagrant-hostmanager`
|
||||||
|
|
||||||
3. Create the directory `pony.fm.files` in the repository's parent directory
|
3. Install the `vagrant-bindfs` plugin: `vagrant plugin install vagrant-bindfs`
|
||||||
|
|
||||||
4. Run `vagrant up` from the folder in which you cloned the repository
|
4. Run `vagrant up` from the folder in which you cloned the repository
|
||||||
|
|
||||||
|
@ -62,21 +63,13 @@ And then install all of the required local packages by invoking:
|
||||||
|
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
Finally, build all of the scripts by executing:
|
Finally, to compile and serve the assets in real time, run the following (and leave it running while you develop):
|
||||||
|
|
||||||
gulp build
|
|
||||||
|
|
||||||
During development, you should make a point to run "gulp watch". You can do this simply by executing:
|
|
||||||
|
|
||||||
gulp watch
|
gulp watch
|
||||||
|
|
||||||
This will watch and compile the `.less` and `.coffee` files in real time.
|
|
||||||
|
|
||||||
Configuring the servers
|
Configuring the servers
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
Pony.fm uses nginx, php-fpm, redis, and MySQL. You can modify the configuration of these services by locating the appropriate config file in the `vagrant` folder. Once modified, you must reload the configuration by running the appropriate shell script (`reload-config.sh`) or bat files (`reload-config.bat` and `reload-config.vmware.bat`). These scripts simply tell Vagrant to run `copy-and-restart-config.sh` on the VM.
|
Pony.fm uses nginx, php-fpm, redis, and MySQL. You can modify the configuration of these services by locating the appropriate config file in the `vagrant` folder. Once modified, you must reload the configuration by running the appropriate shell script (`reload-config.sh`) or bat files (`reload-config.bat` and `reload-config.vmware.bat`). These scripts simply tell Vagrant to run `copy-and-restart-config.sh` on the VM.
|
||||||
|
|
||||||
If you need to change any other configuration file on the VM - copy the entire file over into the vagrant folder, make your changes, and update the `copy-and-restart-config.sh` script to copy the modified config back into the proper folder. All potential configuration requirements should be represented in the `vagrant` folder **and never only on the VM itself** as changes will not be preserved.
|
If you need to change any other configuration file on the VM - copy the entire file over into the vagrant folder, make your changes, and update the `copy-and-restart-config.sh` script to copy the modified config back into the proper folder. All potential configuration requirements should be represented in the `vagrant` folder **and never only on the VM itself** as changes will not be preserved.
|
||||||
|
|
||||||
**NOTE:** currently, Redis's configuration is not reloaded by the `copy-and-restart-config.sh`
|
|
||||||
|
|
14
Vagrantfile
vendored
14
Vagrantfile
vendored
|
@ -3,11 +3,12 @@ Vagrant.configure("2") do |config|
|
||||||
config.hostmanager.enabled = true
|
config.hostmanager.enabled = true
|
||||||
config.hostmanager.manage_host = true
|
config.hostmanager.manage_host = true
|
||||||
|
|
||||||
config.vm.box = 'laravel/homestead-7'
|
config.vm.box = 'laravel/homestead'
|
||||||
config.vm.box_version = '0.2.1'
|
config.vm.box_version = '0.4.2'
|
||||||
|
|
||||||
config.vm.provider "virtualbox" do |v|
|
config.vm.provider "virtualbox" do |v|
|
||||||
v.cpus = 4
|
v.cpus = 4
|
||||||
v.memory = 2048
|
v.memory = 1024
|
||||||
end
|
end
|
||||||
|
|
||||||
config.vm.define 'default' do |node|
|
config.vm.define 'default' do |node|
|
||||||
|
@ -17,13 +18,8 @@ Vagrant.configure("2") do |config|
|
||||||
end
|
end
|
||||||
|
|
||||||
config.vm.synced_folder ".", "/vagrant", type: "nfs"
|
config.vm.synced_folder ".", "/vagrant", type: "nfs"
|
||||||
|
|
||||||
config.vm.provision "shell", path: "vagrant/install.sh"
|
|
||||||
|
|
||||||
config.vm.network "forwarded_port", guest: 3306, host: 33060
|
|
||||||
|
|
||||||
config.vm.synced_folder "../pony.fm.files", "/vagrant-files", type: "nfs"
|
|
||||||
config.bindfs.bind_folder "/vagrant", "/vagrant"
|
config.bindfs.bind_folder "/vagrant", "/vagrant"
|
||||||
|
|
||||||
|
config.vm.provision "shell", path: "vagrant/install.sh"
|
||||||
config.vm.provision "shell", path: "vagrant/copy-and-restart-configs.sh", run: "always"
|
config.vm.provision "shell", path: "vagrant/copy-and-restart-configs.sh", run: "always"
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,11 +20,19 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm;
|
||||||
|
|
||||||
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
use ZipStream;
|
use ZipStream;
|
||||||
|
|
||||||
class AlbumDownloader
|
class AlbumDownloader
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var Album
|
||||||
|
*/
|
||||||
private $_album;
|
private $_album;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $_format;
|
private $_format;
|
||||||
|
|
||||||
function __construct($album, $format)
|
function __construct($album, $format)
|
||||||
|
|
|
@ -20,14 +20,18 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Playlist;
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Auth;
|
||||||
use Illuminate\Support\Facades\DB;
|
use DB;
|
||||||
|
use Validator;
|
||||||
|
|
||||||
class AddTrackToPlaylistCommand extends CommandBase
|
class AddTrackToPlaylistCommand extends CommandBase
|
||||||
{
|
{
|
||||||
|
/** @var Track */
|
||||||
private $_track;
|
private $_track;
|
||||||
|
|
||||||
|
/** @var Playlist */
|
||||||
private $_playlist;
|
private $_playlist;
|
||||||
|
|
||||||
function __construct($playlistId, $trackId)
|
function __construct($playlistId, $trackId)
|
||||||
|
@ -52,10 +56,22 @@ class AddTrackToPlaylistCommand extends CommandBase
|
||||||
*/
|
*/
|
||||||
public function execute()
|
public function execute()
|
||||||
{
|
{
|
||||||
|
// check if this track is already in the playlist
|
||||||
|
$validator = Validator::make(
|
||||||
|
['track_id' => $this->_track->id],
|
||||||
|
['track_id' => "unique:playlist_track,track_id,null,id,playlist_id,{$this->_playlist->id}",]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$songIndex = $this->_playlist->tracks()->count() + 1;
|
$songIndex = $this->_playlist->tracks()->count() + 1;
|
||||||
$this->_playlist->tracks()->attach($this->_track, ['position' => $songIndex]);
|
$this->_playlist->tracks()->attach($this->_track, ['position' => $songIndex]);
|
||||||
|
$this->_playlist->touch();
|
||||||
|
|
||||||
Playlist::whereId($this->_playlist->id)->update([
|
Playlist::where('id', $this->_playlist->id)->update([
|
||||||
'track_count' => DB::raw('(SELECT COUNT(id) FROM playlist_track WHERE playlist_id = ' . $this->_playlist->id . ')')
|
'track_count' => DB::raw('(SELECT COUNT(id) FROM playlist_track WHERE playlist_id = ' . $this->_playlist->id . ')')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Album;
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
use Poniverse\Ponyfm\Image;
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,11 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Album;
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
use Poniverse\Ponyfm\Comment;
|
use Poniverse\Ponyfm\Models\Comment;
|
||||||
use Poniverse\Ponyfm\Playlist;
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Poniverse\Ponyfm\User;
|
use Poniverse\Ponyfm\Models\User;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
|
75
app/Commands/CreateGenreCommand.php
Normal file
75
app/Commands/CreateGenreCommand.php
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2016 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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\Commands;
|
||||||
|
|
||||||
|
use Gate;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Poniverse\Ponyfm\Models\Genre;
|
||||||
|
use Validator;
|
||||||
|
|
||||||
|
class CreateGenreCommand extends CommandBase
|
||||||
|
{
|
||||||
|
/** @var Genre */
|
||||||
|
private $_genreName;
|
||||||
|
|
||||||
|
public function __construct($genreName)
|
||||||
|
{
|
||||||
|
$this->_genreName = $genreName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return Gate::allows('create-genre');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$slug = Str::slug($this->_genreName);
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'name' => 'required|unique:genres,name,NULL,id,deleted_at,NULL|max:50',
|
||||||
|
'slug' => 'required|unique:genres,slug,NULL,id,deleted_at,NULL'
|
||||||
|
];
|
||||||
|
|
||||||
|
$validator = Validator::make([
|
||||||
|
'name' => $this->_genreName,
|
||||||
|
'slug' => $slug
|
||||||
|
], $rules);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
Genre::create([
|
||||||
|
'name' => $this->_genreName,
|
||||||
|
'slug' => $slug
|
||||||
|
]);
|
||||||
|
|
||||||
|
return CommandResponse::succeed(['message' => 'Genre created!']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Playlist;
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
|
|
@ -20,18 +20,21 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Album;
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Auth;
|
||||||
|
|
||||||
class DeleteAlbumCommand extends CommandBase
|
class DeleteAlbumCommand extends CommandBase
|
||||||
{
|
{
|
||||||
|
/** @var int */
|
||||||
private $_albumId;
|
private $_albumId;
|
||||||
|
|
||||||
|
/** @var Album */
|
||||||
private $_album;
|
private $_album;
|
||||||
|
|
||||||
function __construct($albumId)
|
function __construct($albumId)
|
||||||
{
|
{
|
||||||
$this->_albumId = $albumId;
|
$this->_albumId = $albumId;
|
||||||
$this->_album = ALbum::find($albumId);
|
$this->_album = Album::find($albumId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Gate;
|
use Gate;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Poniverse\Ponyfm\Genre;
|
use Poniverse\Ponyfm\Models\Genre;
|
||||||
use Poniverse\Ponyfm\Jobs\DeleteGenre;
|
use Poniverse\Ponyfm\Jobs\DeleteGenre;
|
||||||
use Validator;
|
use Validator;
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,15 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Playlist;
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Auth;
|
||||||
|
|
||||||
class DeletePlaylistCommand extends CommandBase
|
class DeletePlaylistCommand extends CommandBase
|
||||||
{
|
{
|
||||||
|
/** @var int */
|
||||||
private $_playlistId;
|
private $_playlistId;
|
||||||
|
|
||||||
|
/** @var Playlist */
|
||||||
private $_playlist;
|
private $_playlist;
|
||||||
|
|
||||||
function __construct($playlistId)
|
function __construct($playlistId)
|
||||||
|
|
|
@ -20,11 +20,15 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Track;
|
use Gate;
|
||||||
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
|
|
||||||
class DeleteTrackCommand extends CommandBase
|
class DeleteTrackCommand extends CommandBase
|
||||||
{
|
{
|
||||||
|
/** @var int */
|
||||||
private $_trackId;
|
private $_trackId;
|
||||||
|
|
||||||
|
/** @var Track */
|
||||||
private $_track;
|
private $_track;
|
||||||
|
|
||||||
function __construct($trackId)
|
function __construct($trackId)
|
||||||
|
@ -38,9 +42,7 @@ class DeleteTrackCommand extends CommandBase
|
||||||
*/
|
*/
|
||||||
public function authorize()
|
public function authorize()
|
||||||
{
|
{
|
||||||
$user = \Auth::user();
|
return Gate::allows('delete', $this->_track);
|
||||||
|
|
||||||
return $this->_track && $user != null && $this->_track->user_id == $user->id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,16 +20,18 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Album;
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
use Poniverse\Ponyfm\Image;
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Auth;
|
||||||
use Illuminate\Support\Facades\DB;
|
use DB;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Validator;
|
||||||
|
|
||||||
class EditAlbumCommand extends CommandBase
|
class EditAlbumCommand extends CommandBase
|
||||||
{
|
{
|
||||||
private $_input;
|
private $_input;
|
||||||
|
/** @var int */
|
||||||
private $_albumId;
|
private $_albumId;
|
||||||
|
/** @var Album */
|
||||||
private $_album;
|
private $_album;
|
||||||
|
|
||||||
function __construct($trackId, $input)
|
function __construct($trackId, $input)
|
||||||
|
@ -88,10 +90,6 @@ class EditAlbumCommand extends CommandBase
|
||||||
$this->_album->syncTrackIds($trackIds);
|
$this->_album->syncTrackIds($trackIds);
|
||||||
$this->_album->save();
|
$this->_album->save();
|
||||||
|
|
||||||
Album::where('id', $this->_album->id)->update([
|
|
||||||
'track_count' => DB::raw('(SELECT COUNT(id) FROM tracks WHERE album_id = ' . $this->_album->id . ')')
|
|
||||||
]);
|
|
||||||
|
|
||||||
return CommandResponse::succeed(['real_cover_url' => $this->_album->getCoverUrl(Image::NORMAL)]);
|
return CommandResponse::succeed(['real_cover_url' => $this->_album->getCoverUrl(Image::NORMAL)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\PinnedPlaylist;
|
use Poniverse\Ponyfm\Models\PinnedPlaylist;
|
||||||
use Poniverse\Ponyfm\Playlist;
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,12 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Album;
|
use Gate;
|
||||||
use Poniverse\Ponyfm\Image;
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
use Poniverse\Ponyfm\TrackType;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Poniverse\Ponyfm\User;
|
use Poniverse\Ponyfm\Models\TrackType;
|
||||||
|
use Poniverse\Ponyfm\Models\User;
|
||||||
use Auth;
|
use Auth;
|
||||||
use DB;
|
use DB;
|
||||||
|
|
||||||
|
@ -46,9 +47,7 @@ class EditTrackCommand extends CommandBase
|
||||||
*/
|
*/
|
||||||
public function authorize()
|
public function authorize()
|
||||||
{
|
{
|
||||||
$user = \Auth::user();
|
return $this->_track && Gate::allows('edit', $this->_track);
|
||||||
|
|
||||||
return $this->_track && $user != null && $this->_track->user_id == $user->id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,8 +60,11 @@ class EditTrackCommand extends CommandBase
|
||||||
|
|
||||||
$rules = [
|
$rules = [
|
||||||
'title' => 'required|min:3|max:80',
|
'title' => 'required|min:3|max:80',
|
||||||
'released_at' => 'before:' . (date('Y-m-d',
|
'released_at' => 'before:' .
|
||||||
time() + (86400 * 2))) . (isset($this->_input['released_at']) && $this->_input['released_at'] != "" ? '|date' : ''),
|
(date('Y-m-d', time() + (86400 * 2))) . (
|
||||||
|
isset($this->_input['released_at']) && $this->_input['released_at'] != ""
|
||||||
|
? '|date'
|
||||||
|
: ''),
|
||||||
'license_id' => 'required|exists:licenses,id',
|
'license_id' => 'required|exists:licenses,id',
|
||||||
'genre_id' => 'required|exists:genres,id',
|
'genre_id' => 'required|exists:genres,id',
|
||||||
'cover' => 'image|mimes:png,jpeg|min_width:350|min_height:350',
|
'cover' => 'image|mimes:png,jpeg|min_width:350|min_height:350',
|
||||||
|
@ -140,7 +142,7 @@ class EditTrackCommand extends CommandBase
|
||||||
} else {
|
} else {
|
||||||
if (isset($this->_input['cover'])) {
|
if (isset($this->_input['cover'])) {
|
||||||
$cover = $this->_input['cover'];
|
$cover = $this->_input['cover'];
|
||||||
$track->cover_id = Image::upload($cover, Auth::user())->id;
|
$track->cover_id = Image::upload($cover, $track->user_id)->id;
|
||||||
} else {
|
} else {
|
||||||
if ($this->_input['remove_cover'] == 'true') {
|
if ($this->_input['remove_cover'] == 'true') {
|
||||||
$track->cover_id = null;
|
$track->cover_id = null;
|
||||||
|
|
187
app/Commands/GenerateTrackFilesCommand.php
Normal file
187
app/Commands/GenerateTrackFilesCommand.php
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2016 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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\Commands;
|
||||||
|
|
||||||
|
use FFmpegMovie;
|
||||||
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
|
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;
|
||||||
|
use Validator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This command is the "second phase" of the upload process - once metadata has
|
||||||
|
* been parsed and the track object is created, this generates the track's
|
||||||
|
* corresponding TrackFile objects and ensures that all of them have been encoded.
|
||||||
|
*
|
||||||
|
* @package Poniverse\Ponyfm\Commands
|
||||||
|
*/
|
||||||
|
class GenerateTrackFilesCommand extends CommandBase
|
||||||
|
{
|
||||||
|
use DispatchesJobs;
|
||||||
|
|
||||||
|
private $track;
|
||||||
|
private $autoPublish;
|
||||||
|
private $sourceFile;
|
||||||
|
|
||||||
|
protected static $_losslessFormats = [
|
||||||
|
'flac',
|
||||||
|
'pcm',
|
||||||
|
'adpcm',
|
||||||
|
'alac'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(Track $track, SplFileInfo $sourceFile, bool $autoPublish = false)
|
||||||
|
{
|
||||||
|
$this->track = $track;
|
||||||
|
$this->autoPublish = $autoPublish;
|
||||||
|
$this->sourceFile = $sourceFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$source = $this->sourceFile->getPathname();
|
||||||
|
|
||||||
|
// Lossy uploads need to be identified and set as the master file
|
||||||
|
// without being re-encoded.
|
||||||
|
$audioObject = AudioCache::get($source);
|
||||||
|
$isLossyUpload = !$this->isLosslessFile($audioObject);
|
||||||
|
$codecString = $audioObject->getAudioCodec();
|
||||||
|
|
||||||
|
if ($isLossyUpload) {
|
||||||
|
if ($codecString === 'mp3') {
|
||||||
|
$masterFormat = 'MP3';
|
||||||
|
|
||||||
|
} else if (Str::startsWith($codecString, 'aac')) {
|
||||||
|
$masterFormat = 'AAC';
|
||||||
|
|
||||||
|
} else if ($codecString === 'vorbis') {
|
||||||
|
$masterFormat = 'OGG Vorbis';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->track->delete();
|
||||||
|
return CommandResponse::fail(['track' => "The track does not contain audio in a known lossy format. The format read from the file is: {$codecString}"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check: skip creating this TrackFile if it already exists.
|
||||||
|
$trackFile = $this->trackFileExists($masterFormat);
|
||||||
|
|
||||||
|
if (!$trackFile) {
|
||||||
|
$trackFile = new TrackFile();
|
||||||
|
$trackFile->is_master = true;
|
||||||
|
$trackFile->format = $masterFormat;
|
||||||
|
$trackFile->track_id = $this->track->id;
|
||||||
|
$trackFile->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lossy masters are copied into the datastore - no re-encoding involved.
|
||||||
|
File::copy($source, $trackFile->getFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$trackFiles = [];
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check: skip creating this TrackFile if it already exists.
|
||||||
|
// But, we'll still encode it!
|
||||||
|
if ($trackFile = $this->trackFileExists($name)) {
|
||||||
|
$trackFiles[] = $trackFile;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$trackFile = new TrackFile();
|
||||||
|
$trackFile->is_master = $name === 'FLAC' ? true : false;
|
||||||
|
$trackFile->format = $name;
|
||||||
|
$trackFile->status = TrackFile::STATUS_PROCESSING_PENDING;
|
||||||
|
|
||||||
|
if (in_array($name, Track::$CacheableFormats) && !$trackFile->is_master) {
|
||||||
|
$trackFile->is_cacheable = true;
|
||||||
|
} else {
|
||||||
|
$trackFile->is_cacheable = false;
|
||||||
|
}
|
||||||
|
$this->track->trackFiles()->save($trackFile);
|
||||||
|
|
||||||
|
// All TrackFile records we need are synchronously created
|
||||||
|
// before kicking off the encode jobs in order to avoid a race
|
||||||
|
// condition with the "temporary" source file getting deleted.
|
||||||
|
$trackFiles[] = $trackFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($trackFiles as $trackFile) {
|
||||||
|
$this->dispatch(new EncodeTrackFile($trackFile, false, true, $this->autoPublish));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (InvalidEncodeOptionsException $e) {
|
||||||
|
$this->track->delete();
|
||||||
|
return CommandResponse::fail(['track' => [$e->getMessage()]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->track->delete();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommandResponse::succeed([
|
||||||
|
'id' => $this->track->id,
|
||||||
|
'name' => $this->track->name,
|
||||||
|
'title' => $this->track->title,
|
||||||
|
'slug' => $this->track->slug,
|
||||||
|
'autoPublish' => $this->autoPublish,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) {
|
||||||
|
if (is_string($file)) {
|
||||||
|
$file = AudioCache::get($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Str::startsWith($file->getAudioCodec(), static::$_losslessFormats);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $format
|
||||||
|
* @return TrackFile|null
|
||||||
|
*/
|
||||||
|
private function trackFileExists(string $format) {
|
||||||
|
return $this->track->trackFiles()->where('format', $format)->first();
|
||||||
|
}
|
||||||
|
}
|
120
app/Commands/MergeAccountsCommand.php
Normal file
120
app/Commands/MergeAccountsCommand.php
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2016 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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\Commands;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use DB;
|
||||||
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
|
use Poniverse\Ponyfm\Models\Comment;
|
||||||
|
use Poniverse\Ponyfm\Models\Favourite;
|
||||||
|
use Poniverse\Ponyfm\Models\Follower;
|
||||||
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
|
use Poniverse\Ponyfm\Models\PinnedPlaylist;
|
||||||
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
|
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||||
|
use Poniverse\Ponyfm\Models\ResourceUser;
|
||||||
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
|
use Poniverse\Ponyfm\Models\User;
|
||||||
|
|
||||||
|
class MergeAccountsCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $sourceAccount;
|
||||||
|
private $destinationAccount;
|
||||||
|
|
||||||
|
function __construct(User $sourceAccount, User $destinationAccount)
|
||||||
|
{
|
||||||
|
$this->sourceAccount = $sourceAccount;
|
||||||
|
$this->destinationAccount = $destinationAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
DB::transaction(function() {
|
||||||
|
$accountIds = [$this->sourceAccount->id];
|
||||||
|
|
||||||
|
foreach (Album::whereIn('user_id', $accountIds)->get() as $album) {
|
||||||
|
$album->user_id = $this->destinationAccount->id;
|
||||||
|
$album->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Comment::whereIn('user_id', $accountIds)->get() as $comment) {
|
||||||
|
$comment->user_id = $this->destinationAccount->id;
|
||||||
|
$comment->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Favourite::whereIn('user_id', $accountIds)->get() as $favourite) {
|
||||||
|
$favourite->user_id = $this->destinationAccount->id;
|
||||||
|
$favourite->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Follower::whereIn('artist_id', $accountIds)->get() as $follow) {
|
||||||
|
$follow->artist_id = $this->destinationAccount->id;
|
||||||
|
$follow->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Image::whereIn('uploaded_by', $accountIds)->get() as $image) {
|
||||||
|
$image->uploaded_by = $this->destinationAccount->id;
|
||||||
|
$image->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Image::whereIn('uploaded_by', $accountIds)->get() as $image) {
|
||||||
|
$image->uploaded_by = $this->destinationAccount->id;
|
||||||
|
$image->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::table('oauth2_tokens')->whereIn('user_id', $accountIds)->update(['user_id' => $this->destinationAccount->id]);
|
||||||
|
|
||||||
|
foreach (PinnedPlaylist::whereIn('user_id', $accountIds)->get() as $playlist) {
|
||||||
|
$playlist->user_id = $this->destinationAccount->id;
|
||||||
|
$playlist->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Playlist::whereIn('user_id', $accountIds)->get() as $playlist) {
|
||||||
|
$playlist->user_id = $this->destinationAccount->id;
|
||||||
|
$playlist->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ResourceLogItem::whereIn('user_id', $accountIds)->get() as $item) {
|
||||||
|
$item->user_id = $this->destinationAccount->id;
|
||||||
|
$item->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ResourceUser::whereIn('user_id', $accountIds)->get() as $item) {
|
||||||
|
$item->user_id = $this->destinationAccount->id;
|
||||||
|
$item->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Track::whereIn('user_id', $accountIds)->get() as $track) {
|
||||||
|
$track->user_id = $this->destinationAccount->id;
|
||||||
|
$track->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->sourceAccount->disabled_at = Carbon::now();
|
||||||
|
$this->sourceAccount->save();
|
||||||
|
});
|
||||||
|
|
||||||
|
return CommandResponse::succeed();
|
||||||
|
}
|
||||||
|
}
|
441
app/Commands/ParseTrackTagsCommand.php
Normal file
441
app/Commands/ParseTrackTagsCommand.php
Normal file
|
@ -0,0 +1,441 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2016 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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\Commands;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Config;
|
||||||
|
use getID3;
|
||||||
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
|
use Poniverse\Ponyfm\Models\Genre;
|
||||||
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
|
use AudioCache;
|
||||||
|
use File;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Poniverse\Ponyfm\Models\TrackType;
|
||||||
|
use Poniverse\Ponyfm\Models\User;
|
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
|
|
||||||
|
|
||||||
|
class ParseTrackTagsCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $track;
|
||||||
|
private $fileToParse;
|
||||||
|
private $input;
|
||||||
|
|
||||||
|
public function __construct(Track $track, \Symfony\Component\HttpFoundation\File\File $fileToParse, $inputTags = [])
|
||||||
|
{
|
||||||
|
$this->track = $track;
|
||||||
|
$this->fileToParse = $fileToParse;
|
||||||
|
$this->input = $inputTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$audio = AudioCache::get($this->fileToParse->getPathname());
|
||||||
|
list($parsedTags, $rawTags) = $this->parseOriginalTags($this->fileToParse, $this->track->user, $audio->getAudioCodec());
|
||||||
|
$this->track->original_tags = ['parsed_tags' => $parsedTags, 'raw_tags' => $rawTags];
|
||||||
|
|
||||||
|
|
||||||
|
if ($this->input['cover'] !== null) {
|
||||||
|
$this->track->cover_id = Image::upload($this->input['cover'], $this->track->user_id)->id;
|
||||||
|
} else {
|
||||||
|
$this->track->cover_id = $parsedTags['cover_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->track->title = $this->input['title'] ?? $parsedTags['title'] ?? $this->track->title;
|
||||||
|
$this->track->track_type_id = $this->input['track_type_id'] ?? TrackType::UNCLASSIFIED_TRACK;
|
||||||
|
|
||||||
|
$this->track->genre_id = isset($this->input['genre'])
|
||||||
|
? $this->getGenreId($this->input['genre'])
|
||||||
|
: $parsedTags['genre_id'];
|
||||||
|
|
||||||
|
$this->track->album_id = isset($this->input['album'])
|
||||||
|
? $this->getAlbumId($this->track->user_id, $this->input['album'])
|
||||||
|
: $parsedTags['album_id'];
|
||||||
|
|
||||||
|
if ($this->track->album_id === null) {
|
||||||
|
$this->track->track_number = null;
|
||||||
|
} else {
|
||||||
|
$this->track->track_number = $this->input['track_number'] ?? $parsedTags['track_number'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->track->released_at = isset($this->input['released_at'])
|
||||||
|
? Carbon::createFromFormat(Carbon::ISO8601, $this->input['released_at'])
|
||||||
|
: $parsedTags['release_date'];
|
||||||
|
|
||||||
|
$this->track->description = $this->input['description'] ?? $parsedTags['comments'];
|
||||||
|
$this->track->lyrics = $this->input['lyrics'] ?? $parsedTags['lyrics'];
|
||||||
|
|
||||||
|
$this->track->is_vocal = $this->input['is_vocal'] ?? $parsedTags['is_vocal'];
|
||||||
|
$this->track->is_explicit = $this->input['is_explicit'] ?? false;
|
||||||
|
$this->track->is_downloadable = $this->input['is_downloadable'] ?? true;
|
||||||
|
$this->track->is_listed = $this->input['is_listed'] ?? true;
|
||||||
|
|
||||||
|
$this->track->save();
|
||||||
|
return CommandResponse::succeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the given genre, creating it if necessary.
|
||||||
|
*
|
||||||
|
* @param string $genreName
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function getGenreId(string $genreName) {
|
||||||
|
return Genre::firstOrCreate([
|
||||||
|
'name' => $genreName,
|
||||||
|
'slug' => Str::slug($genreName)
|
||||||
|
])->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the given album, creating it if necessary.
|
||||||
|
* The cover ID is only used if a new album is created - it will not be
|
||||||
|
* written to an existing album.
|
||||||
|
*
|
||||||
|
* @param int $artistId
|
||||||
|
* @param string|null $albumName
|
||||||
|
* @param null $coverId
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
protected function getAlbumId(int $artistId, $albumName, $coverId = null) {
|
||||||
|
if (null !== $albumName) {
|
||||||
|
$album = Album::firstOrNew([
|
||||||
|
'user_id' => $artistId,
|
||||||
|
'title' => $albumName
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (null === $album->id) {
|
||||||
|
$album->description = '';
|
||||||
|
$album->track_count = 0;
|
||||||
|
$album->cover_id = $coverId;
|
||||||
|
$album->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $album->id;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts a file's tags.
|
||||||
|
*
|
||||||
|
* @param \Symfony\Component\HttpFoundation\File\File $file
|
||||||
|
* @param User $artist
|
||||||
|
* @param string $audioCodec
|
||||||
|
* @return array the "processed" and raw tags extracted from the file
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
protected function parseOriginalTags(\Symfony\Component\HttpFoundation\File\File $file, User $artist, string $audioCodec) {
|
||||||
|
//==========================================================================================================
|
||||||
|
// Extract the original tags.
|
||||||
|
//==========================================================================================================
|
||||||
|
$getId3 = new getID3;
|
||||||
|
|
||||||
|
// all tags read by getID3, including the cover art
|
||||||
|
$allTags = $getId3->analyze($file->getPathname());
|
||||||
|
|
||||||
|
// $rawTags => tags specific to a file format (ID3 or Atom), pre-normalization but with cover art removed
|
||||||
|
// $parsedTags => normalized tags used by Pony.fm
|
||||||
|
|
||||||
|
if ($audioCodec === 'mp3') {
|
||||||
|
list($parsedTags, $rawTags) = $this->getId3Tags($allTags);
|
||||||
|
|
||||||
|
} elseif (Str::startsWith($audioCodec, ['aac', 'alac'])) {
|
||||||
|
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
||||||
|
|
||||||
|
} elseif (in_array($audioCodec, ['vorbis', 'flac'])) {
|
||||||
|
list($parsedTags, $rawTags) = $this->getVorbisTags($allTags);
|
||||||
|
|
||||||
|
} elseif (Str::startsWith($audioCodec, ['pcm', 'adpcm'])) {
|
||||||
|
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Assume the file is untagged if it's in an unknown format.
|
||||||
|
$parsedTags = [
|
||||||
|
'title' => null,
|
||||||
|
'artist' => null,
|
||||||
|
'band' => null,
|
||||||
|
'genre' => null,
|
||||||
|
'track_number' => null,
|
||||||
|
'album' => null,
|
||||||
|
'year' => null,
|
||||||
|
'release_date' => null,
|
||||||
|
'comments' => null,
|
||||||
|
'lyrics' => null,
|
||||||
|
];
|
||||||
|
$rawTags = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Determine the release date.
|
||||||
|
//==========================================================================================================
|
||||||
|
if ($parsedTags['release_date'] === null && $parsedTags['year'] !== null) {
|
||||||
|
$parsedTags['release_date'] = Carbon::create($parsedTags['year'], 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Does this track have vocals?
|
||||||
|
//==========================================================================================================
|
||||||
|
$parsedTags['is_vocal'] = $parsedTags['lyrics'] !== null;
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Determine the genre.
|
||||||
|
//==========================================================================================================
|
||||||
|
$genreName = $parsedTags['genre'];
|
||||||
|
|
||||||
|
if ($genreName !== null) {
|
||||||
|
$parsedTags['genre_id'] = $this->getGenreId($genreName);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$parsedTags['genre_id'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Extract the cover art, if any exists.
|
||||||
|
//==========================================================================================================
|
||||||
|
|
||||||
|
$coverId = null;
|
||||||
|
if (array_key_exists('comments', $allTags) && array_key_exists('picture', $allTags['comments'])) {
|
||||||
|
$image = $allTags['comments']['picture'][0];
|
||||||
|
|
||||||
|
if ($image['image_mime'] === 'image/png') {
|
||||||
|
$extension = 'png';
|
||||||
|
|
||||||
|
} elseif ($image['image_mime'] === 'image/jpeg') {
|
||||||
|
$extension = 'jpg';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new BadRequestHttpException('Unknown cover format embedded in the track file!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// write temporary image file
|
||||||
|
$tmpPath = Config::get('ponyfm.files_directory') . '/tmp';
|
||||||
|
|
||||||
|
$filename = $file->getFilename() . ".cover.${extension}";
|
||||||
|
$imageFilePath = "${tmpPath}/${filename}";
|
||||||
|
|
||||||
|
File::put($imageFilePath, $image['data']);
|
||||||
|
$imageFile = new UploadedFile($imageFilePath, $filename, $image['image_mime']);
|
||||||
|
|
||||||
|
$cover = Image::upload($imageFile, $artist);
|
||||||
|
$coverId = $cover->id;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// no cover art was found - carry on
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsedTags['cover_id'] = $coverId;
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Is this part of an album?
|
||||||
|
//==========================================================================================================
|
||||||
|
$albumId = null;
|
||||||
|
$albumName = $parsedTags['album'];
|
||||||
|
|
||||||
|
if ($albumName !== null) {
|
||||||
|
$albumId = $this->getAlbumId($artist->id, $albumName, $coverId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsedTags['album_id'] = $albumId;
|
||||||
|
|
||||||
|
|
||||||
|
return [$parsedTags, $rawTags];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $rawTags
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getId3Tags($rawTags) {
|
||||||
|
if (array_key_exists('tags', $rawTags) && array_key_exists('id3v2', $rawTags['tags'])) {
|
||||||
|
$tags = $rawTags['tags']['id3v2'];
|
||||||
|
} elseif (array_key_exists('tags', $rawTags) && array_key_exists('id3v1', $rawTags['tags'])) {
|
||||||
|
$tags = $rawTags['tags']['id3v1'];
|
||||||
|
} else {
|
||||||
|
$tags = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$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 [
|
||||||
|
[
|
||||||
|
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
||||||
|
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
||||||
|
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||||
|
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
||||||
|
'track_number' => isset($tags['track_number']) ? $tags['track_number'][0] : null,
|
||||||
|
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||||
|
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
||||||
|
'release_date' => isset($tags['release_date']) ? $this->parseDateString($tags['release_date'][0]) : null,
|
||||||
|
'comments' => $comment,
|
||||||
|
'lyrics' => isset($tags['unsynchronised_lyric']) ? $tags['unsynchronised_lyric'][0] : null,
|
||||||
|
],
|
||||||
|
$tags
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $rawTags
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getAtomTags($rawTags) {
|
||||||
|
if (array_key_exists('tags', $rawTags) && array_key_exists('quicktime', $rawTags['tags'])) {
|
||||||
|
$tags = $rawTags['tags']['quicktime'];
|
||||||
|
} else {
|
||||||
|
$tags = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$trackNumber = null;
|
||||||
|
if (isset($tags['track_number'])) {
|
||||||
|
$trackNumberComponents = explode('/', $tags['track_number'][0]);
|
||||||
|
$trackNumber = $trackNumberComponents[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($tags['release_date'])) {
|
||||||
|
$releaseDate = $this->parseDateString($tags['release_date'][0]);
|
||||||
|
|
||||||
|
} elseif (isset($tags['creation_date'])) {
|
||||||
|
$releaseDate = $this->parseDateString($tags['creation_date'][0]);
|
||||||
|
} else {
|
||||||
|
$releaseDate = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
||||||
|
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
||||||
|
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||||
|
'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null,
|
||||||
|
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
||||||
|
'track_number' => $trackNumber,
|
||||||
|
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||||
|
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
||||||
|
'release_date' => $releaseDate,
|
||||||
|
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
||||||
|
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
||||||
|
],
|
||||||
|
$tags
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $rawTags
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getVorbisTags($rawTags) {
|
||||||
|
if (array_key_exists('tags', $rawTags) && array_key_exists('vorbiscomment', $rawTags['tags'])) {
|
||||||
|
$tags = $rawTags['tags']['vorbiscomment'];
|
||||||
|
} else {
|
||||||
|
$tags = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$trackNumber = null;
|
||||||
|
if (isset($tags['track_number'])) {
|
||||||
|
$trackNumberComponents = explode('/', $tags['track_number'][0]);
|
||||||
|
$trackNumber = $trackNumberComponents[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
||||||
|
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
||||||
|
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||||
|
'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null,
|
||||||
|
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
||||||
|
'track_number' => $trackNumber,
|
||||||
|
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||||
|
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
||||||
|
'release_date' => isset($tags['date']) ? $this->parseDateString($tags['date'][0]) : null,
|
||||||
|
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
||||||
|
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
||||||
|
],
|
||||||
|
$tags
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a potentially-partial date string into a proper date object.
|
||||||
|
*
|
||||||
|
* The tagging formats we deal with base their date format on ISO 8601, but
|
||||||
|
* the timestamp may be incomplete.
|
||||||
|
*
|
||||||
|
* @link https://code.google.com/p/mp4v2/wiki/iTunesMetadata
|
||||||
|
* @link https://wiki.xiph.org/VorbisComment#Date_and_time
|
||||||
|
* @link http://id3.org/id3v2.4.0-frames
|
||||||
|
*
|
||||||
|
* @param string $dateString
|
||||||
|
* @return null|Carbon
|
||||||
|
*/
|
||||||
|
protected function parseDateString(string $dateString) {
|
||||||
|
switch (Str::length($dateString)) {
|
||||||
|
// YYYY
|
||||||
|
case 4:
|
||||||
|
return Carbon::createFromFormat('Y', $dateString)
|
||||||
|
->month(1)
|
||||||
|
->day(1);
|
||||||
|
|
||||||
|
// YYYY-MM
|
||||||
|
case 7:
|
||||||
|
return Carbon::createFromFormat('Y-m', $dateString)
|
||||||
|
->day(1);
|
||||||
|
|
||||||
|
// YYYY-MM-DD
|
||||||
|
case 10:
|
||||||
|
return Carbon::createFromFormat('Y-m-d', $dateString);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// We might have an ISO-8601 string in our hooves.
|
||||||
|
// If not, give up.
|
||||||
|
try {
|
||||||
|
return Carbon::createFromFormat(Carbon::ISO8601, $dateString);
|
||||||
|
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,12 +21,16 @@
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Gate;
|
use Gate;
|
||||||
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Poniverse\Ponyfm\Genre;
|
use Poniverse\Ponyfm\Jobs\UpdateTagsForRenamedGenre;
|
||||||
|
use Poniverse\Ponyfm\Models\Genre;
|
||||||
use Validator;
|
use Validator;
|
||||||
|
|
||||||
class RenameGenreCommand extends CommandBase
|
class RenameGenreCommand extends CommandBase
|
||||||
{
|
{
|
||||||
|
use DispatchesJobs;
|
||||||
|
|
||||||
/** @var Genre */
|
/** @var Genre */
|
||||||
private $_genre;
|
private $_genre;
|
||||||
private $_newName;
|
private $_newName;
|
||||||
|
@ -72,6 +76,8 @@ class RenameGenreCommand extends CommandBase
|
||||||
$this->_genre->slug = $slug;
|
$this->_genre->slug = $slug;
|
||||||
$this->_genre->save();
|
$this->_genre->save();
|
||||||
|
|
||||||
|
$this->dispatch(new UpdateTagsForRenamedGenre($this->_genre));
|
||||||
|
|
||||||
return CommandResponse::succeed(['message' => 'Genre renamed!']);
|
return CommandResponse::succeed(['message' => 'Genre renamed!']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Image;
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Favourite;
|
use Poniverse\Ponyfm\Models\Favourite;
|
||||||
use Poniverse\Ponyfm\ResourceUser;
|
use Poniverse\Ponyfm\Models\ResourceUser;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Commands;
|
namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Follower;
|
use Poniverse\Ponyfm\Models\Follower;
|
||||||
use Poniverse\Ponyfm\ResourceUser;
|
use Poniverse\Ponyfm\Models\ResourceUser;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class ToggleFollowingCommand extends CommandBase
|
class ToggleFollowingCommand extends CommandBase
|
||||||
|
|
|
@ -22,46 +22,30 @@ namespace Poniverse\Ponyfm\Commands;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Config;
|
use Config;
|
||||||
use getID3;
|
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Input;
|
use Input;
|
||||||
use Poniverse\Ponyfm\Album;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
|
|
||||||
use Poniverse\Ponyfm\Genre;
|
|
||||||
use Poniverse\Ponyfm\Image;
|
|
||||||
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
|
||||||
use Poniverse\Ponyfm\Track;
|
|
||||||
use Poniverse\Ponyfm\TrackFile;
|
|
||||||
use AudioCache;
|
use AudioCache;
|
||||||
use File;
|
use Validator;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Poniverse\Ponyfm\TrackType;
|
|
||||||
use Poniverse\Ponyfm\User;
|
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
|
||||||
|
|
||||||
class UploadTrackCommand extends CommandBase
|
class UploadTrackCommand extends CommandBase
|
||||||
{
|
{
|
||||||
use DispatchesJobs;
|
use DispatchesJobs;
|
||||||
|
|
||||||
|
|
||||||
private $_allowLossy;
|
private $_allowLossy;
|
||||||
private $_allowShortTrack;
|
private $_allowShortTrack;
|
||||||
private $_customTrackSource;
|
private $_customTrackSource;
|
||||||
private $_autoPublishByDefault;
|
private $_autoPublishByDefault;
|
||||||
|
|
||||||
private $_losslessFormats = [
|
/**
|
||||||
'flac',
|
* UploadTrackCommand constructor.
|
||||||
'pcm_s16le ([1][0][0][0] / 0x0001)',
|
*
|
||||||
'pcm_s16be',
|
* @param bool $allowLossy
|
||||||
'adpcm_ms ([2][0][0][0] / 0x0002)',
|
* @param bool $allowShortTrack allow tracks shorter than 30 seconds
|
||||||
'pcm_s24le ([1][0][0][0] / 0x0001)',
|
* @param string|null $customTrackSource value to set in the track's "source" field; if left blank, "direct_upload" is used
|
||||||
'pcm_s24be',
|
* @param bool $autoPublishByDefault
|
||||||
'pcm_f32le ([3][0][0][0] / 0x0003)',
|
*/
|
||||||
'pcm_f32be (fl32 / 0x32336C66)'
|
public function __construct(bool $allowLossy = false, bool $allowShortTrack = false, string $customTrackSource = null, bool $autoPublishByDefault = false)
|
||||||
];
|
|
||||||
|
|
||||||
public function __construct($allowLossy = false, $allowShortTrack = false, $customTrackSource = null, $autoPublishByDefault = false)
|
|
||||||
{
|
{
|
||||||
$this->_allowLossy = $allowLossy;
|
$this->_allowLossy = $allowLossy;
|
||||||
$this->_allowShortTrack = $allowShortTrack;
|
$this->_allowShortTrack = $allowShortTrack;
|
||||||
|
@ -84,22 +68,21 @@ class UploadTrackCommand extends CommandBase
|
||||||
public function execute()
|
public function execute()
|
||||||
{
|
{
|
||||||
$user = \Auth::user();
|
$user = \Auth::user();
|
||||||
$trackFile = \Input::file('track', null);
|
$trackFile = Input::file('track', null);
|
||||||
|
$coverFile = Input::file('cover', null);
|
||||||
|
|
||||||
if (null === $trackFile) {
|
if (null === $trackFile) {
|
||||||
return CommandResponse::fail(['track' => ['You must upload an audio file!']]);
|
return CommandResponse::fail(['track' => ['You must upload an audio file!']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$audio = \AudioCache::get($trackFile->getPathname());
|
$audio = \AudioCache::get($trackFile->getPathname());
|
||||||
list($parsedTags, $rawTags) = $this->parseOriginalTags($trackFile, $user, $audio->getAudioCodec());
|
|
||||||
|
|
||||||
|
|
||||||
$track = new Track();
|
$track = new Track();
|
||||||
$track->user_id = $user->id;
|
$track->user_id = $user->id;
|
||||||
$track->title = Input::get('title', $parsedTags['title']);
|
// 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->duration = $audio->getDuration();
|
||||||
|
|
||||||
|
|
||||||
$track->save();
|
$track->save();
|
||||||
$track->ensureDirectoryExists();
|
$track->ensureDirectoryExists();
|
||||||
|
|
||||||
|
@ -110,11 +93,14 @@ class UploadTrackCommand extends CommandBase
|
||||||
|
|
||||||
$input = Input::all();
|
$input = Input::all();
|
||||||
$input['track'] = $trackFile;
|
$input['track'] = $trackFile;
|
||||||
|
$input['cover'] = $coverFile;
|
||||||
|
|
||||||
$validator = \Validator::make($input, [
|
$validator = \Validator::make($input, [
|
||||||
'track' =>
|
'track' =>
|
||||||
'required|'
|
'required|'
|
||||||
. ($this->_allowLossy ? '' : 'audio_format:'. implode(',', $this->_losslessFormats).'|')
|
. ($this->_allowLossy
|
||||||
|
? 'audio_format:flac,alac,pcm,adpcm,aac,mp3,vorbis|'
|
||||||
|
: 'audio_format:flac,alac,pcm,adpcm|')
|
||||||
. ($this->_allowShortTrack ? '' : 'min_duration:30|')
|
. ($this->_allowShortTrack ? '' : 'min_duration:30|')
|
||||||
. 'audio_channels:1,2',
|
. 'audio_channels:1,2',
|
||||||
|
|
||||||
|
@ -139,467 +125,22 @@ class UploadTrackCommand extends CommandBase
|
||||||
$track->delete();
|
$track->delete();
|
||||||
return CommandResponse::fail($validator);
|
return CommandResponse::fail($validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Process optional track fields
|
|
||||||
$autoPublish = (bool) ($input['auto_publish'] ?? $this->_autoPublishByDefault);
|
$autoPublish = (bool) ($input['auto_publish'] ?? $this->_autoPublishByDefault);
|
||||||
|
|
||||||
if (Input::hasFile('cover')) {
|
|
||||||
$track->cover_id = Image::upload(Input::file('cover'), $track->user_id)->id;
|
|
||||||
} else {
|
|
||||||
$track->cover_id = $parsedTags['cover_id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$track->title = $input['title'] ?? $parsedTags['title'] ?? $track->title;
|
|
||||||
$track->track_type_id = $input['track_type_id'] ?? TrackType::UNCLASSIFIED_TRACK;
|
|
||||||
|
|
||||||
$track->genre_id = isset($input['genre'])
|
|
||||||
? $this->getGenreId($input['genre'])
|
|
||||||
: $parsedTags['genre_id'];
|
|
||||||
|
|
||||||
$track->album_id = isset($input['album'])
|
|
||||||
? $this->getAlbumId($user->id, $input['album'])
|
|
||||||
: $parsedTags['album_id'];
|
|
||||||
|
|
||||||
if ($track->album_id === null) {
|
|
||||||
$track->track_number = null;
|
|
||||||
} else {
|
|
||||||
$track->track_number = $input['track_number'] ?? $parsedTags['track_number'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$track->released_at = isset($input['released_at'])
|
|
||||||
? Carbon::createFromFormat(Carbon::ISO8601, $input['released_at'])
|
|
||||||
: $parsedTags['release_date'];
|
|
||||||
|
|
||||||
$track->description = $input['description'] ?? $parsedTags['comments'];
|
|
||||||
$track->lyrics = $input['lyrics'] ?? $parsedTags['lyrics'];
|
|
||||||
|
|
||||||
$track->is_vocal = $input['is_vocal'] ?? $parsedTags['is_vocal'];
|
|
||||||
$track->is_explicit = $input['is_explicit'] ?? false;
|
|
||||||
$track->is_downloadable = $input['is_downloadable'] ?? true;
|
|
||||||
$track->is_listed = $input['is_listed'] ?? true;
|
|
||||||
$track->source = $this->_customTrackSource ?? 'direct_upload';
|
$track->source = $this->_customTrackSource ?? 'direct_upload';
|
||||||
|
|
||||||
// If json_decode() isn't called here, Laravel will surround the JSON
|
// If json_decode() isn't called here, Laravel will surround the JSON
|
||||||
// string with quotes when storing it in the database, which breaks things.
|
// string with quotes when storing it in the database, which breaks things.
|
||||||
$track->metadata = json_decode(Input::get('metadata', null));
|
$track->metadata = json_decode(Input::get('metadata', null));
|
||||||
$track->original_tags = ['parsed_tags' => $parsedTags, 'raw_tags' => $rawTags];
|
|
||||||
|
|
||||||
$track->save();
|
$track->save();
|
||||||
|
|
||||||
|
// Parse any tags in the uploaded files.
|
||||||
try {
|
$parseTagsCommand = new ParseTrackTagsCommand($track, $trackFile, $input);
|
||||||
$source = $trackFile->getPathname();
|
$result = $parseTagsCommand->execute();
|
||||||
|
if ($result->didFail()) {
|
||||||
// Lossy uploads need to be identified and set as the master file
|
return $result;
|
||||||
// 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 if ($audioObject->getAudioCodec() === 'vorbis') {
|
|
||||||
$masterFormat = 'OGG Vorbis';
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$validator->messages()->add('track', 'The track does not contain audio in a known lossy format.');
|
|
||||||
$track->delete();
|
|
||||||
return CommandResponse::fail($validator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$trackFile = new TrackFile();
|
$generateTrackFiles = new GenerateTrackFilesCommand($track, $trackFile, $autoPublish);
|
||||||
$trackFile->is_master = true;
|
return $generateTrackFiles->execute();
|
||||||
$trackFile->format = $masterFormat;
|
|
||||||
$trackFile->track_id = $track->id;
|
|
||||||
$trackFile->save();
|
|
||||||
|
|
||||||
// Lossy masters are copied into the datastore - no re-encoding involved.
|
|
||||||
File::copy($source, $trackFile->getFile());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$trackFiles = [];
|
|
||||||
|
|
||||||
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->is_master = $name === 'FLAC' ? true : false;
|
|
||||||
$trackFile->format = $name;
|
|
||||||
$trackFile->status = TrackFile::STATUS_PROCESSING;
|
|
||||||
|
|
||||||
if (in_array($name, Track::$CacheableFormats) && $trackFile->is_master == false) {
|
|
||||||
$trackFile->is_cacheable = true;
|
|
||||||
} else {
|
|
||||||
$trackFile->is_cacheable = false;
|
|
||||||
}
|
|
||||||
$track->trackFiles()->save($trackFile);
|
|
||||||
|
|
||||||
// All TrackFile records we need are synchronously created
|
|
||||||
// before kicking off the encode jobs in order to avoid a race
|
|
||||||
// condition with the "temporary" source file getting deleted.
|
|
||||||
$trackFiles[] = $trackFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
foreach($trackFiles as $trackFile) {
|
|
||||||
$this->dispatch(new EncodeTrackFile($trackFile, false, true, $autoPublish));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (InvalidEncodeOptionsException $e) {
|
|
||||||
$track->delete();
|
|
||||||
return CommandResponse::fail(['track' => [$e->getMessage()]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$track->delete();
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CommandResponse::succeed([
|
|
||||||
'id' => $track->id,
|
|
||||||
'name' => $track->name,
|
|
||||||
'title' => $track->title,
|
|
||||||
'slug' => $track->slug,
|
|
||||||
'autoPublish' => $autoPublish,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the ID of the given genre, creating it if necessary.
|
|
||||||
*
|
|
||||||
* @param string $genreName
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
protected function getGenreId(string $genreName) {
|
|
||||||
return Genre::firstOrCreate([
|
|
||||||
'name' => $genreName,
|
|
||||||
'slug' => Str::slug($genreName)
|
|
||||||
])->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the ID of the given album, creating it if necessary.
|
|
||||||
* The cover ID is only used if a new album is created - it will not be
|
|
||||||
* written to an existing album.
|
|
||||||
*
|
|
||||||
* @param int $artistId
|
|
||||||
* @param string|null $albumName
|
|
||||||
* @param null $coverId
|
|
||||||
* @return int|null
|
|
||||||
*/
|
|
||||||
protected function getAlbumId(int $artistId, $albumName, $coverId = null) {
|
|
||||||
if (null !== $albumName) {
|
|
||||||
$album = Album::firstOrNew([
|
|
||||||
'user_id' => $artistId,
|
|
||||||
'title' => $albumName
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (null === $album->id) {
|
|
||||||
$album->description = '';
|
|
||||||
$album->track_count = 0;
|
|
||||||
$album->cover_id = $coverId;
|
|
||||||
$album->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $album->id;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts a file's tags.
|
|
||||||
*
|
|
||||||
* @param UploadedFile $file
|
|
||||||
* @param User $artist
|
|
||||||
* @param string $audioCodec
|
|
||||||
* @return array the "processed" and raw tags extracted from the file
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
protected function parseOriginalTags(UploadedFile $file, User $artist, string $audioCodec) {
|
|
||||||
//==========================================================================================================
|
|
||||||
// Extract the original tags.
|
|
||||||
//==========================================================================================================
|
|
||||||
$getId3 = new getID3;
|
|
||||||
|
|
||||||
// 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 = [];
|
|
||||||
|
|
||||||
if ($audioCodec === 'mp3') {
|
|
||||||
list($parsedTags, $rawTags) = $this->getId3Tags($allTags);
|
|
||||||
|
|
||||||
} elseif (Str::startsWith($audioCodec, 'aac')) {
|
|
||||||
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
|
||||||
|
|
||||||
} elseif ($audioCodec === 'vorbis') {
|
|
||||||
list($parsedTags, $rawTags) = $this->getVorbisTags($allTags);
|
|
||||||
|
|
||||||
} elseif ($audioCodec === 'flac') {
|
|
||||||
list($parsedTags, $rawTags) = $this->getVorbisTags($allTags);
|
|
||||||
|
|
||||||
} elseif (Str::startsWith($audioCodec, ['pcm', 'adpcm'])) {
|
|
||||||
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Fill in the title tag if it's missing
|
|
||||||
//==========================================================================================================
|
|
||||||
$parsedTags['title'] = $parsedTags['title'] ?? pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Determine the release date.
|
|
||||||
//==========================================================================================================
|
|
||||||
if ($parsedTags['release_date'] === null && $parsedTags['year'] !== null) {
|
|
||||||
$parsedTags['release_date'] = Carbon::create($parsedTags['year'], 1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Does this track have vocals?
|
|
||||||
//==========================================================================================================
|
|
||||||
$parsedTags['is_vocal'] = $parsedTags['lyrics'] !== null;
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Determine the genre.
|
|
||||||
//==========================================================================================================
|
|
||||||
$genreName = $parsedTags['genre'];
|
|
||||||
|
|
||||||
if ($genreName) {
|
|
||||||
$parsedTags['genre_id'] = $this->getGenreId($genreName);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$parsedTags['genre_id'] = $this->getGenreId('Unknown');
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Extract the cover art, if any exists.
|
|
||||||
//==========================================================================================================
|
|
||||||
|
|
||||||
$coverId = null;
|
|
||||||
if (array_key_exists('comments', $allTags) && array_key_exists('picture', $allTags['comments'])) {
|
|
||||||
$image = $allTags['comments']['picture'][0];
|
|
||||||
|
|
||||||
if ($image['image_mime'] === 'image/png') {
|
|
||||||
$extension = 'png';
|
|
||||||
|
|
||||||
} elseif ($image['image_mime'] === 'image/jpeg') {
|
|
||||||
$extension = 'jpg';
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new BadRequestHttpException('Unknown cover format embedded in the track file!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// write temporary image file
|
|
||||||
$tmpPath = Config::get('ponyfm.files_directory') . '/tmp';
|
|
||||||
|
|
||||||
$filename = $file->getFilename() . ".cover.${extension}";
|
|
||||||
$imageFilePath = "${tmpPath}/${filename}";
|
|
||||||
|
|
||||||
File::put($imageFilePath, $image['data']);
|
|
||||||
$imageFile = new UploadedFile($imageFilePath, $filename, $image['image_mime']);
|
|
||||||
|
|
||||||
$cover = Image::upload($imageFile, $artist);
|
|
||||||
$coverId = $cover->id;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// no cover art was found - carry on
|
|
||||||
}
|
|
||||||
|
|
||||||
$parsedTags['cover_id'] = $coverId;
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Is this part of an album?
|
|
||||||
//==========================================================================================================
|
|
||||||
$albumId = null;
|
|
||||||
$albumName = $parsedTags['album'];
|
|
||||||
|
|
||||||
if ($albumName !== null) {
|
|
||||||
$albumId = $this->getAlbumId($artist->id, $albumName, $coverId);
|
|
||||||
}
|
|
||||||
|
|
||||||
$parsedTags['album_id'] = $albumId;
|
|
||||||
|
|
||||||
|
|
||||||
return [$parsedTags, $rawTags];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $rawTags
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function getId3Tags($rawTags) {
|
|
||||||
if (array_key_exists('tags', $rawTags) && array_key_exists('id3v2', $rawTags['tags'])) {
|
|
||||||
$tags = $rawTags['tags']['id3v2'];
|
|
||||||
} elseif (array_key_exists('tags', $rawTags) && array_key_exists('id3v1', $rawTags['tags'])) {
|
|
||||||
$tags = $rawTags['tags']['id3v1'];
|
|
||||||
} else {
|
|
||||||
$tags = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$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 [
|
|
||||||
[
|
|
||||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
|
||||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
|
||||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
|
||||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
|
||||||
'track_number' => isset($tags['track_number']) ? $tags['track_number'][0] : null,
|
|
||||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
|
||||||
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
|
||||||
'release_date' => isset($tags['release_date']) ? $this->parseDateString($tags['release_date'][0]) : null,
|
|
||||||
'comments' => $comment,
|
|
||||||
'lyrics' => isset($tags['unsynchronised_lyric']) ? $tags['unsynchronised_lyric'][0] : null,
|
|
||||||
],
|
|
||||||
$tags
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $rawTags
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function getAtomTags($rawTags) {
|
|
||||||
if (array_key_exists('tags', $rawTags) && array_key_exists('quicktime', $rawTags['tags'])) {
|
|
||||||
$tags = $rawTags['tags']['quicktime'];
|
|
||||||
} else {
|
|
||||||
$tags = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$trackNumber = null;
|
|
||||||
if (isset($tags['track_number'])) {
|
|
||||||
$trackNumberComponents = explode('/', $tags['track_number'][0]);
|
|
||||||
$trackNumber = $trackNumberComponents[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
|
||||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
|
||||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
|
||||||
'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null,
|
|
||||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
|
||||||
'track_number' => $trackNumber,
|
|
||||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
|
||||||
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
|
||||||
'release_date' => isset($tags['release_date']) ? $this->parseDateString($tags['release_date'][0]) : null,
|
|
||||||
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
|
||||||
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
|
||||||
],
|
|
||||||
$tags
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $rawTags
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function getVorbisTags($rawTags) {
|
|
||||||
if (array_key_exists('tags', $rawTags) && array_key_exists('vorbiscomment', $rawTags['tags'])) {
|
|
||||||
$tags = $rawTags['tags']['vorbiscomment'];
|
|
||||||
} else {
|
|
||||||
$tags = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$trackNumber = null;
|
|
||||||
if (isset($tags['track_number'])) {
|
|
||||||
$trackNumberComponents = explode('/', $tags['track_number'][0]);
|
|
||||||
$trackNumber = $trackNumberComponents[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
|
||||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
|
||||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
|
||||||
'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null,
|
|
||||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
|
||||||
'track_number' => $trackNumber,
|
|
||||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
|
||||||
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
|
||||||
'release_date' => isset($tags['date']) ? $this->parseDateString($tags['date'][0]) : null,
|
|
||||||
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
|
||||||
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
|
||||||
],
|
|
||||||
$tags
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a potentially-partial date string into a proper date object.
|
|
||||||
*
|
|
||||||
* The tagging formats we deal with base their date format on ISO 8601, but
|
|
||||||
* the timestamp may be incomplete.
|
|
||||||
*
|
|
||||||
* @link https://code.google.com/p/mp4v2/wiki/iTunesMetadata
|
|
||||||
* @link https://wiki.xiph.org/VorbisComment#Date_and_time
|
|
||||||
* @link http://id3.org/id3v2.4.0-frames
|
|
||||||
*
|
|
||||||
* @param string $dateString
|
|
||||||
* @return null|Carbon
|
|
||||||
*/
|
|
||||||
protected function parseDateString(string $dateString) {
|
|
||||||
switch (Str::length($dateString)) {
|
|
||||||
// YYYY
|
|
||||||
case 4:
|
|
||||||
return Carbon::createFromFormat('Y', $dateString)
|
|
||||||
->month(1)
|
|
||||||
->day(1);
|
|
||||||
|
|
||||||
// YYYY-MM
|
|
||||||
case 7:
|
|
||||||
return Carbon::createFromFormat('Y-m', $dateString)
|
|
||||||
->day(1);
|
|
||||||
|
|
||||||
// YYYY-MM-DD
|
|
||||||
case 10:
|
|
||||||
return Carbon::createFromFormat('Y-m-d', $dateString);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// We might have an ISO-8601 string in our hooves.
|
|
||||||
// If not, give up.
|
|
||||||
try {
|
|
||||||
return Carbon::createFromFormat(Carbon::ISO8601, $dateString);
|
|
||||||
|
|
||||||
} catch (\InvalidArgumentException $e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,9 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Console\Commands;
|
namespace Poniverse\Ponyfm\Console\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\ShowSong;
|
use Poniverse\Ponyfm\Models\ShowSong;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Poniverse\Ponyfm\TrackType;
|
use Poniverse\Ponyfm\Models\TrackType;
|
||||||
use DB;
|
use DB;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
|
@ -24,7 +24,7 @@ use Carbon\Carbon;
|
||||||
use File;
|
use File;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Poniverse\Ponyfm\TrackFile;
|
use Poniverse\Ponyfm\Models\TrackFile;
|
||||||
|
|
||||||
class ClearTrackCache extends Command
|
class ClearTrackCache extends Command
|
||||||
{
|
{
|
||||||
|
@ -96,9 +96,6 @@ class ClearTrackCache extends Command
|
||||||
$this->info('Deleted ' . $trackFile->getFile());
|
$this->info('Deleted ' . $trackFile->getFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the cached file size for the album
|
|
||||||
Cache::forget($trackFile->track->album->getCacheKey('filesize-' . $trackFile->format));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
$this->info($count . ' files deleted. Deletion complete. Exiting.');
|
$this->info($count . ' files deleted. Deletion complete. Exiting.');
|
||||||
} else {
|
} else {
|
||||||
|
|
154
app/Console/Commands/FixMLPMAImages.php
Normal file
154
app/Console/Commands/FixMLPMAImages.php
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2016 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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 Config;
|
||||||
|
use DB;
|
||||||
|
use File;
|
||||||
|
use getID3;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
|
||||||
|
class FixMLPMAImages extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'mlpma:fix-images
|
||||||
|
{--startAt=1 : Track to start importing from. Useful for resuming an interrupted import.}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Re-imports MLPMA cover art';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $currentFile;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File extensions to ignore when importing the archive.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $ignoredExtensions = [
|
||||||
|
'db',
|
||||||
|
'jpg',
|
||||||
|
'png',
|
||||||
|
'txt',
|
||||||
|
'rtf',
|
||||||
|
'wma',
|
||||||
|
'wmv'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$mlpmaPath = Config::get('ponyfm.files_directory') . '/mlpma';
|
||||||
|
$tmpPath = Config::get('ponyfm.files_directory') . '/tmp';
|
||||||
|
|
||||||
|
$this->comment('Enumerating MLP Music Archive source files...');
|
||||||
|
$files = File::allFiles($mlpmaPath);
|
||||||
|
$this->info(sizeof($files) . ' files found!');
|
||||||
|
|
||||||
|
$this->comment('Importing tracks...');
|
||||||
|
$totalFiles = sizeof($files);
|
||||||
|
$fileToStartAt = (int) $this->option('startAt') - 1;
|
||||||
|
|
||||||
|
$this->comment("Skipping $fileToStartAt files..." . PHP_EOL);
|
||||||
|
$files = array_slice($files, $fileToStartAt);
|
||||||
|
$this->currentFile = $fileToStartAt;
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$this->currentFile ++;
|
||||||
|
|
||||||
|
$this->info('[' . $this->currentFile . '/' . $totalFiles . '] Importing track [' . $file->getFilename() . ']...');
|
||||||
|
if (in_array($file->getExtension(), $this->ignoredExtensions)) {
|
||||||
|
$this->comment('This is not an audio file! Skipping...' . PHP_EOL);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Get this track's MLPMA record
|
||||||
|
$importedTrack = DB::table('mlpma_tracks')
|
||||||
|
->where('filename', '=', $file->getFilename())
|
||||||
|
->join('tracks', 'mlpma_tracks.track_id', '=', 'tracks.id')
|
||||||
|
->first();
|
||||||
|
$artistId = $importedTrack->user_id;
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Extract the original tags.
|
||||||
|
//==========================================================================================================
|
||||||
|
|
||||||
|
$getId3 = new getID3;
|
||||||
|
// all tags read by getID3, including the cover art
|
||||||
|
$allTags = $getId3->analyze($file->getPathname());
|
||||||
|
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Extract the cover art, if any exists.
|
||||||
|
//==========================================================================================================
|
||||||
|
$coverId = null;
|
||||||
|
|
||||||
|
if (array_key_exists('comments', $allTags) && array_key_exists('picture', $allTags['comments'])) {
|
||||||
|
$this->comment('Extracting cover art!');
|
||||||
|
$image = $allTags['comments']['picture'][0];
|
||||||
|
if ($image['image_mime'] === 'image/png') {
|
||||||
|
$extension = 'png';
|
||||||
|
} elseif ($image['image_mime'] === 'image/jpeg') {
|
||||||
|
$extension = 'jpg';
|
||||||
|
} elseif ($image['image_mime'] === 'image/gif') {
|
||||||
|
$extension = 'gif';
|
||||||
|
} else {
|
||||||
|
$this->error('Unknown cover art format!');
|
||||||
|
}
|
||||||
|
// write temporary image file
|
||||||
|
$imageFilename = $file->getFilename() . ".cover.$extension";
|
||||||
|
$imageFilePath = "$tmpPath/" . $imageFilename;
|
||||||
|
File::put($imageFilePath, $image['data']);
|
||||||
|
$imageFile = new UploadedFile($imageFilePath, $imageFilename, $image['image_mime']);
|
||||||
|
$cover = Image::upload($imageFile, $artistId, true);
|
||||||
|
$coverId = $cover->id;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->comment('No cover art found!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,87 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pony.fm - A community for pony fan music.
|
|
||||||
* Copyright (C) 2015 Peter Deltchev
|
|
||||||
*
|
|
||||||
* 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 Carbon\Carbon;
|
|
||||||
use Poniverse\Ponyfm\ResourceLogItem;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
|
|
||||||
class FixYearZeroLogs extends Command
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'poni:year-zero';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Fills in missing timestamps in the resource_log_items table.';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$items = ResourceLogItem::where('created_at', '0000-00-00 00:00:00')->orderBy('id', 'asc')->get();
|
|
||||||
$totalItems = $items->count();
|
|
||||||
|
|
||||||
// calculate the start and end of the logging gap
|
|
||||||
$lastGoodId = (int) $items[0]->id - 1;
|
|
||||||
$lastGoodItem = ResourceLogItem::find($lastGoodId);
|
|
||||||
|
|
||||||
$lastGoodDate = $lastGoodItem->created_at;
|
|
||||||
$now = Carbon::now();
|
|
||||||
|
|
||||||
$secondsDifference = $now->diffInSeconds($lastGoodDate);
|
|
||||||
$oneInterval = $secondsDifference / $totalItems;
|
|
||||||
|
|
||||||
$this->info('Correcting records...');
|
|
||||||
$bar = $this->output->createProgressBar($totalItems);
|
|
||||||
|
|
||||||
foreach ($items as $i => $item) {
|
|
||||||
$bar->advance();
|
|
||||||
$dateOffset = (int) ($oneInterval * $i);
|
|
||||||
$item->created_at = $lastGoodDate->copy()->addSeconds($dateOffset);
|
|
||||||
$item->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
$bar->finish();
|
|
||||||
$this->line('');
|
|
||||||
$this->info('All done!');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,519 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pony.fm - A community for pony fan music.
|
|
||||||
* Copyright (C) 2015 Peter Deltchev
|
|
||||||
*
|
|
||||||
* 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 Poniverse\Ponyfm\Album;
|
|
||||||
use Poniverse\Ponyfm\Commands\UploadTrackCommand;
|
|
||||||
use Poniverse\Ponyfm\Genre;
|
|
||||||
use Poniverse\Ponyfm\Image;
|
|
||||||
use Poniverse\Ponyfm\Track;
|
|
||||||
use Poniverse\Ponyfm\User;
|
|
||||||
use Auth;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Config;
|
|
||||||
use DB;
|
|
||||||
use File;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Input;
|
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|
||||||
use getID3;
|
|
||||||
|
|
||||||
|
|
||||||
class ImportMLPMA extends Command
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'mlpma:import
|
|
||||||
{--startAt=1 : Track to start importing from. Useful for resuming an interrupted import.}';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Imports the MLP Music Archive';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* File extensions to ignore when importing the archive.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $ignoredExtensions = ['db', 'jpg', 'png', 'txt', 'rtf', 'wma', 'wmv'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to stop the import process when a SIGINT is received.
|
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
protected $isInterrupted = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A counter for the number of processed tracks.
|
|
||||||
*
|
|
||||||
* @var int
|
|
||||||
*/
|
|
||||||
protected $currentFile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handleInterrupt($signo)
|
|
||||||
{
|
|
||||||
$this->error('Import aborted!');
|
|
||||||
$this->error('Resume it from here using: --startAt=' . $this->currentFile);
|
|
||||||
$this->isInterrupted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
pcntl_signal(SIGINT, [$this, 'handleInterrupt']);
|
|
||||||
|
|
||||||
$mlpmaPath = Config::get('ponyfm.files_directory') . '/mlpma';
|
|
||||||
$tmpPath = Config::get('ponyfm.files_directory') . '/tmp';
|
|
||||||
|
|
||||||
if (!File::exists($tmpPath)) {
|
|
||||||
File::makeDirectory($tmpPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
$UNKNOWN_GENRE = Genre::firstOrCreate([
|
|
||||||
'name' => 'Unknown',
|
|
||||||
'slug' => 'unknown'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->comment('Enumerating MLP Music Archive source files...');
|
|
||||||
$files = File::allFiles($mlpmaPath);
|
|
||||||
$this->info(sizeof($files) . ' files found!');
|
|
||||||
|
|
||||||
$this->comment('Enumerating artists...');
|
|
||||||
$artists = File::directories($mlpmaPath);
|
|
||||||
$this->info(sizeof($artists) . ' artists found!');
|
|
||||||
|
|
||||||
$this->comment('Importing tracks...');
|
|
||||||
|
|
||||||
$totalFiles = sizeof($files);
|
|
||||||
|
|
||||||
$fileToStartAt = (int)$this->option('startAt') - 1;
|
|
||||||
$this->comment("Skipping $fileToStartAt files..." . PHP_EOL);
|
|
||||||
|
|
||||||
$files = array_slice($files, $fileToStartAt);
|
|
||||||
$this->currentFile = $fileToStartAt;
|
|
||||||
|
|
||||||
foreach ($files as $file) {
|
|
||||||
$this->currentFile++;
|
|
||||||
|
|
||||||
pcntl_signal_dispatch();
|
|
||||||
if ($this->isInterrupted) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->comment('[' . $this->currentFile . '/' . $totalFiles . '] Importing track [' . $file->getFilename() . ']...');
|
|
||||||
|
|
||||||
if (in_array($file->getExtension(), $this->ignoredExtensions)) {
|
|
||||||
$this->comment('This is not an audio file! Skipping...' . PHP_EOL);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
//==========================================================================================================
|
|
||||||
$getId3 = new getID3;
|
|
||||||
|
|
||||||
// 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 = [];
|
|
||||||
|
|
||||||
if (Str::lower($file->getExtension()) === 'mp3') {
|
|
||||||
list($parsedTags, $rawTags) = $this->getId3Tags($allTags);
|
|
||||||
|
|
||||||
} elseif (Str::lower($file->getExtension()) === 'm4a') {
|
|
||||||
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
|
||||||
|
|
||||||
} elseif (Str::lower($file->getExtension()) === 'ogg') {
|
|
||||||
list($parsedTags, $rawTags) = $this->getVorbisTags($allTags);
|
|
||||||
|
|
||||||
} elseif (Str::lower($file->getExtension()) === 'flac') {
|
|
||||||
list($parsedTags, $rawTags) = $this->getVorbisTags($allTags);
|
|
||||||
|
|
||||||
} elseif (Str::lower($file->getExtension()) === 'wav') {
|
|
||||||
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Determine the release date.
|
|
||||||
//==========================================================================================================
|
|
||||||
$modifiedDate = Carbon::createFromTimeStampUTC(File::lastModified($file->getPathname()));
|
|
||||||
$taggedYear = $parsedTags['year'];
|
|
||||||
|
|
||||||
$this->info('Modification year: ' . $modifiedDate->year);
|
|
||||||
$this->info('Tagged year: ' . $taggedYear);
|
|
||||||
|
|
||||||
if ($taggedYear !== null && $modifiedDate->year === $taggedYear) {
|
|
||||||
$releasedAt = $modifiedDate;
|
|
||||||
} elseif ($taggedYear !== null && Str::length((string)$taggedYear) !== 4) {
|
|
||||||
$this->error('This track\'s tagged year makes no sense! Using the track\'s last modified date...');
|
|
||||||
$releasedAt = $modifiedDate;
|
|
||||||
} elseif ($taggedYear !== null && $modifiedDate->year !== $taggedYear) {
|
|
||||||
$this->error('Release years don\'t match! Using the tagged year...');
|
|
||||||
$releasedAt = Carbon::create($taggedYear);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// $taggedYear is null
|
|
||||||
$this->error('This track isn\'t tagged with its release year! Using the track\'s last modified date...');
|
|
||||||
$releasedAt = $modifiedDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is later used by the classification/publishing script to determine the publication date.
|
|
||||||
$parsedTags['released_at'] = $releasedAt->toDateTimeString();
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Does this track have vocals?
|
|
||||||
//==========================================================================================================
|
|
||||||
$isVocal = $parsedTags['lyrics'] !== null;
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Fill in the title tag if it's missing.
|
|
||||||
//==========================================================================================================
|
|
||||||
if (!$parsedTags['title']) {
|
|
||||||
$parsedTags['title'] = $file->getBasename('.' . $file->getExtension());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Determine the genre.
|
|
||||||
//==========================================================================================================
|
|
||||||
$genreName = $parsedTags['genre'];
|
|
||||||
$genreSlug = Str::slug($genreName);
|
|
||||||
$this->info('Genre: ' . $genreName);
|
|
||||||
|
|
||||||
if ($genreName && $genreSlug !== '') {
|
|
||||||
$genre = Genre::where('name', '=', $genreName)->first();
|
|
||||||
if ($genre) {
|
|
||||||
$genreId = $genre->id;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$genre = new Genre();
|
|
||||||
$genre->name = $genreName;
|
|
||||||
$genre->slug = $genreSlug;
|
|
||||||
$genre->save();
|
|
||||||
$genreId = $genre->id;
|
|
||||||
$this->comment('Created a new genre!');
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$genreId = $UNKNOWN_GENRE->id; // "Unknown" genre ID
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Determine which artist account this file belongs to using the containing directory.
|
|
||||||
//==========================================================================================================
|
|
||||||
$this->info('Path to file: ' . $file->getRelativePath());
|
|
||||||
$path_components = explode(DIRECTORY_SEPARATOR, $file->getRelativePath());
|
|
||||||
$artist_name = $path_components[0];
|
|
||||||
$album_name = array_key_exists(1, $path_components) ? $path_components[1] : null;
|
|
||||||
|
|
||||||
$this->info('Artist: ' . $artist_name);
|
|
||||||
$this->info('Album: ' . $album_name);
|
|
||||||
|
|
||||||
$artist = User::where('display_name', '=', $artist_name)->first();
|
|
||||||
|
|
||||||
if (!$artist) {
|
|
||||||
$artist = new User;
|
|
||||||
$artist->display_name = $artist_name;
|
|
||||||
$artist->email = null;
|
|
||||||
$artist->is_archived = true;
|
|
||||||
|
|
||||||
$artist->slug = Str::slug($artist_name);
|
|
||||||
|
|
||||||
$slugExists = User::where('slug', '=', $artist->slug)->first();
|
|
||||||
if ($slugExists) {
|
|
||||||
$this->error('Horsefeathers! The slug ' . $artist->slug . ' is already taken!');
|
|
||||||
$artist->slug = $artist->slug . '-' . Str::random(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
$artist->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Extract the cover art, if any exists.
|
|
||||||
//==========================================================================================================
|
|
||||||
|
|
||||||
$this->comment('Extracting cover art!');
|
|
||||||
$coverId = null;
|
|
||||||
if (array_key_exists('comments', $allTags) && array_key_exists('picture', $allTags['comments'])) {
|
|
||||||
$image = $allTags['comments']['picture'][0];
|
|
||||||
|
|
||||||
if ($image['image_mime'] === 'image/png') {
|
|
||||||
$extension = 'png';
|
|
||||||
|
|
||||||
} elseif ($image['image_mime'] === 'image/jpeg') {
|
|
||||||
$extension = 'jpg';
|
|
||||||
|
|
||||||
} elseif ($image['image_mime'] === 'image/gif') {
|
|
||||||
$extension = 'gif';
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$this->error('Unknown cover art format!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// write temporary image file
|
|
||||||
$imageFilename = $file->getFilename() . ".cover.$extension";
|
|
||||||
$imageFilePath = "$tmpPath/" . $imageFilename;
|
|
||||||
File::put($imageFilePath, $image['data']);
|
|
||||||
|
|
||||||
|
|
||||||
$imageFile = new UploadedFile($imageFilePath, $imageFilename, $image['image_mime']);
|
|
||||||
|
|
||||||
$cover = Image::upload($imageFile, $artist);
|
|
||||||
$coverId = $cover->id;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$this->comment('No cover art found!');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Is this part of an album?
|
|
||||||
//==========================================================================================================
|
|
||||||
|
|
||||||
$albumId = null;
|
|
||||||
$albumName = $parsedTags['album'];
|
|
||||||
|
|
||||||
if ($albumName !== null) {
|
|
||||||
$album = Album::where('user_id', '=', $artist->id)
|
|
||||||
->where('title', '=', $albumName)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if (!$album) {
|
|
||||||
$album = new Album;
|
|
||||||
|
|
||||||
$album->title = $albumName;
|
|
||||||
$album->user_id = $artist->id;
|
|
||||||
$album->cover_id = $coverId;
|
|
||||||
|
|
||||||
$album->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
$albumId = $album->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//==========================================================================================================
|
|
||||||
// Save this track.
|
|
||||||
//==========================================================================================================
|
|
||||||
// "Upload" the track to Pony.fm
|
|
||||||
$this->comment('Transcoding the track!');
|
|
||||||
Auth::loginUsingId($artist->id);
|
|
||||||
|
|
||||||
$trackFile = new UploadedFile($file->getPathname(), $file->getFilename(), $allTags['mime_type']);
|
|
||||||
Input::instance()->files->add(['track' => $trackFile]);
|
|
||||||
|
|
||||||
$upload = new UploadTrackCommand(true, true);
|
|
||||||
$result = $upload->execute();
|
|
||||||
|
|
||||||
if ($result->didFail()) {
|
|
||||||
$this->error(json_encode($result->getMessages(), JSON_PRETTY_PRINT));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Save metadata.
|
|
||||||
$track = Track::find($result->getResponse()['id']);
|
|
||||||
|
|
||||||
$track->title = $parsedTags['title'];
|
|
||||||
$track->cover_id = $coverId;
|
|
||||||
$track->album_id = $albumId;
|
|
||||||
$track->genre_id = $genreId;
|
|
||||||
$track->track_number = $parsedTags['track_number'];
|
|
||||||
$track->released_at = $releasedAt;
|
|
||||||
$track->description = $parsedTags['comments'];
|
|
||||||
$track->is_downloadable = true;
|
|
||||||
$track->lyrics = $parsedTags['lyrics'];
|
|
||||||
$track->is_vocal = $isVocal;
|
|
||||||
$track->license_id = 2;
|
|
||||||
$track->save();
|
|
||||||
|
|
||||||
// If we made it to here, the track is intact! Log the import.
|
|
||||||
DB::table('mlpma_tracks')
|
|
||||||
->insert([
|
|
||||||
'track_id' => $result->getResponse()['id'],
|
|
||||||
'path' => $file->getRelativePath(),
|
|
||||||
'filename' => $file->getFilename(),
|
|
||||||
'extension' => $file->getExtension(),
|
|
||||||
'imported_at' => Carbon::now(),
|
|
||||||
'parsed_tags' => json_encode($parsedTags),
|
|
||||||
'raw_tags' => json_encode($rawTags),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
echo PHP_EOL . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $rawTags
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function getId3Tags($rawTags)
|
|
||||||
{
|
|
||||||
if (array_key_exists('tags', $rawTags) && array_key_exists('id3v2', $rawTags['tags'])) {
|
|
||||||
$tags = $rawTags['tags']['id3v2'];
|
|
||||||
} elseif (array_key_exists('tags', $rawTags) && array_key_exists('id3v1', $rawTags['tags'])) {
|
|
||||||
$tags = $rawTags['tags']['id3v1'];
|
|
||||||
} else {
|
|
||||||
$tags = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$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 [
|
|
||||||
[
|
|
||||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
|
||||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
|
||||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
|
||||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
|
||||||
'track_number' => isset($tags['track_number']) ? $tags['track_number'][0] : null,
|
|
||||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
|
||||||
'year' => isset($tags['year']) ? (int)$tags['year'][0] : null,
|
|
||||||
'comments' => $comment,
|
|
||||||
'lyrics' => isset($tags['unsynchronised_lyric']) ? $tags['unsynchronised_lyric'][0] : null,
|
|
||||||
],
|
|
||||||
$tags
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $rawTags
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function getAtomTags($rawTags)
|
|
||||||
{
|
|
||||||
if (array_key_exists('tags', $rawTags) && array_key_exists('quicktime', $rawTags['tags'])) {
|
|
||||||
$tags = $rawTags['tags']['quicktime'];
|
|
||||||
} else {
|
|
||||||
$tags = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$trackNumber = null;
|
|
||||||
if (isset($tags['track_number'])) {
|
|
||||||
$trackNumberComponents = explode('/', $tags['track_number'][0]);
|
|
||||||
$trackNumber = $trackNumberComponents[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
|
||||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
|
||||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
|
||||||
'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null,
|
|
||||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
|
||||||
'track_number' => $trackNumber,
|
|
||||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
|
||||||
'year' => isset($tags['year']) ? (int)$tags['year'][0] : null,
|
|
||||||
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
|
||||||
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
|
||||||
],
|
|
||||||
$tags
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $rawTags
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
protected function getVorbisTags($rawTags)
|
|
||||||
{
|
|
||||||
if (array_key_exists('tags', $rawTags) && array_key_exists('vorbiscomment', $rawTags['tags'])) {
|
|
||||||
$tags = $rawTags['tags']['vorbiscomment'];
|
|
||||||
} else {
|
|
||||||
$tags = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$trackNumber = null;
|
|
||||||
if (isset($tags['track_number'])) {
|
|
||||||
$trackNumberComponents = explode('/', $tags['track_number'][0]);
|
|
||||||
$trackNumber = $trackNumberComponents[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
|
||||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
|
||||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
|
||||||
'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null,
|
|
||||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
|
||||||
'track_number' => $trackNumber,
|
|
||||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
|
||||||
'year' => isset($tags['year']) ? (int)$tags['year'][0] : null,
|
|
||||||
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
|
||||||
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
|
||||||
],
|
|
||||||
$tags
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
86
app/Console/Commands/MergeAccounts.php
Normal file
86
app/Console/Commands/MergeAccounts.php
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2016 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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 Carbon\Carbon;
|
||||||
|
use DB;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Poniverse\Ponyfm\Commands\MergeAccountsCommand;
|
||||||
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
|
use Poniverse\Ponyfm\Models\Comment;
|
||||||
|
use Poniverse\Ponyfm\Models\Favourite;
|
||||||
|
use Poniverse\Ponyfm\Models\Follower;
|
||||||
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
|
use Poniverse\Ponyfm\Models\PinnedPlaylist;
|
||||||
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
|
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||||
|
use Poniverse\Ponyfm\Models\ResourceUser;
|
||||||
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
|
use Poniverse\Ponyfm\Models\User;
|
||||||
|
|
||||||
|
class MergeAccounts extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'accounts:merge
|
||||||
|
{sourceAccountId : ID of the source account (the one being disabled and having content transferred out of it)}
|
||||||
|
{destinationAccountId : ID of the destination account (the one gaining content)}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Merges two accounts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$sourceAccountId = $this->argument('sourceAccountId');
|
||||||
|
$destinationAccountId = $this->argument('destinationAccountId');
|
||||||
|
|
||||||
|
$sourceAccount = User::find($sourceAccountId);
|
||||||
|
$destinationAccount = User::find($destinationAccountId);
|
||||||
|
|
||||||
|
$this->info("Merging {$sourceAccount->display_name} ({$sourceAccountId}) into {$destinationAccount->display_name} ({$destinationAccountId})...");
|
||||||
|
|
||||||
|
$command = new MergeAccountsCommand($sourceAccount, $destinationAccount);
|
||||||
|
$command->execute();
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,8 +20,8 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Console\Commands;
|
namespace Poniverse\Ponyfm\Console\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Image;
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
use Poniverse\Ponyfm\ResourceLogItem;
|
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||||
use DB;
|
use DB;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pony.fm - A community for pony fan music.
|
|
||||||
* Copyright (C) 2015 Peter Deltchev
|
|
||||||
*
|
|
||||||
* 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 Carbon\Carbon;
|
|
||||||
use DB;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Poniverse\Ponyfm\Track;
|
|
||||||
use Poniverse\Ponyfm\TrackType;
|
|
||||||
|
|
||||||
class PublishUnclassifiedMlpmaTracks extends Command
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'mlpma:declassify';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'This publishes all unpublished MLPMA tracks as the "unclassified" track type.';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$affectedTracks = Track::mlpma()->
|
|
||||||
whereNull('published_at')
|
|
||||||
->update([
|
|
||||||
'track_type_id' => TrackType::UNCLASSIFIED_TRACK,
|
|
||||||
'published_at' => DB::raw('released_at'),
|
|
||||||
'updated_at' => Carbon::now(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->info("Updated ${affectedTracks} tracks.");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,7 +21,7 @@
|
||||||
namespace Poniverse\Ponyfm\Console\Commands;
|
namespace Poniverse\Ponyfm\Console\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Poniverse\Ponyfm\User;
|
use Poniverse\Ponyfm\Models\User;
|
||||||
|
|
||||||
class RebuildArtists extends Command
|
class RebuildArtists extends Command
|
||||||
{
|
{
|
||||||
|
@ -56,12 +56,19 @@ class RebuildArtists extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$numberOfUsers = User::count();
|
||||||
|
|
||||||
|
$bar = $this->output->createProgressBar($numberOfUsers);
|
||||||
|
|
||||||
foreach(User::with(['tracks' => function($query) {
|
foreach(User::with(['tracks' => function($query) {
|
||||||
$query->published()->listed();
|
$query->published()->listed();
|
||||||
}])->get() as $user) {
|
}])->get() as $user) {
|
||||||
|
$bar->advance();
|
||||||
$user->track_count = $user->tracks->count();
|
$user->track_count = $user->tracks->count();
|
||||||
$user->save();
|
$user->save();
|
||||||
$this->info('Updated user #'.$user->id.'!');
|
}
|
||||||
}
|
|
||||||
|
$bar->finish();
|
||||||
|
$this->line('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace Poniverse\Ponyfm\Console\Commands;
|
||||||
|
|
||||||
use File;
|
use File;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Poniverse\Ponyfm\TrackFile;
|
use Poniverse\Ponyfm\Models\TrackFile;
|
||||||
|
|
||||||
class RebuildFilesizes extends Command
|
class RebuildFilesizes extends Command
|
||||||
{
|
{
|
||||||
|
|
121
app/Console/Commands/RebuildSearchIndex.php
Normal file
121
app/Console/Commands/RebuildSearchIndex.php
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2016 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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 Illuminate\Console\Command;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
|
use Poniverse\Ponyfm\Models\User;
|
||||||
|
use Symfony\Component\Console\Helper\ProgressBar;
|
||||||
|
|
||||||
|
class RebuildSearchIndex extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'rebuild:search';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Rebuilds the Elasticsearch index.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$totalTracks = Track::withTrashed()->count();
|
||||||
|
$totalAlbums = Album::withTrashed()->count();
|
||||||
|
$totalPlaylists = Playlist::withTrashed()->count();
|
||||||
|
$totalUsers = User::count();
|
||||||
|
|
||||||
|
$trackProgress = $this->output->createProgressBar($totalTracks);
|
||||||
|
$this->info("Processing tracks...");
|
||||||
|
Track::withTrashed()->chunk(200, function(Collection $tracks) use ($trackProgress) {
|
||||||
|
foreach($tracks as $track) {
|
||||||
|
/** @var Track $track */
|
||||||
|
$trackProgress->advance();
|
||||||
|
$track->updateElasticsearchEntry();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$trackProgress->finish();
|
||||||
|
$this->line('');
|
||||||
|
|
||||||
|
|
||||||
|
$albumProgress = $this->output->createProgressBar($totalAlbums);
|
||||||
|
$this->info("Processing albums...");
|
||||||
|
Album::withTrashed()->chunk(200, function(Collection $albums) use ($albumProgress) {
|
||||||
|
foreach($albums as $album) {
|
||||||
|
/** @var Album $album */
|
||||||
|
$albumProgress->advance();
|
||||||
|
$album->updateElasticsearchEntry();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$albumProgress->finish();
|
||||||
|
$this->line('');
|
||||||
|
|
||||||
|
|
||||||
|
$playlistProgress = $this->output->createProgressBar($totalPlaylists);
|
||||||
|
$this->info("Processing playlists...");
|
||||||
|
Playlist::withTrashed()->chunk(200, function(Collection $playlists) use ($playlistProgress) {
|
||||||
|
foreach($playlists as $playlist) {
|
||||||
|
/** @var Playlist $playlist */
|
||||||
|
$playlistProgress->advance();
|
||||||
|
$playlist->updateElasticsearchEntry();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$playlistProgress->finish();
|
||||||
|
$this->line('');
|
||||||
|
|
||||||
|
|
||||||
|
$userProgress = $this->output->createProgressBar($totalUsers);
|
||||||
|
$this->info("Processing users...");
|
||||||
|
User::chunk(200, function(Collection $users) use ($userProgress) {
|
||||||
|
foreach($users as $user) {
|
||||||
|
/** @var User $user */
|
||||||
|
$userProgress->advance();
|
||||||
|
$user->updateElasticsearchEntry();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$userProgress->finish();
|
||||||
|
$this->line('');
|
||||||
|
$this->info('Everything has been queued for re-indexing!');
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Console\Commands;
|
namespace Poniverse\Ponyfm\Console\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
class RebuildTags extends Command
|
class RebuildTags extends Command
|
||||||
|
@ -61,16 +61,18 @@ class RebuildTags extends Command
|
||||||
$tracks = [$track];
|
$tracks = [$track];
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$tracks = Track::whereNotNull('published_at')->orderBy('id', 'asc')->get();
|
$tracks = Track::whereNotNull('published_at')->withTrashed()->orderBy('id', 'asc')->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
$bar = $this->output->createProgressBar(sizeof($tracks));
|
$numberOfTracks = sizeof($tracks);
|
||||||
|
|
||||||
|
$this->info("Updating tags for ${numberOfTracks} tracks...");
|
||||||
|
$bar = $this->output->createProgressBar($numberOfTracks);
|
||||||
|
|
||||||
foreach($tracks as $track) {
|
foreach($tracks as $track) {
|
||||||
$this->comment('Rewriting tags for track #'.$track->id.'...');
|
/** @var $track Track */
|
||||||
$track->updateTags();
|
$track->updateTags();
|
||||||
$bar->advance();
|
$bar->advance();
|
||||||
$this->line('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$bar->finish();
|
$bar->finish();
|
||||||
|
|
105
app/Console/Commands/RebuildTrack.php
Normal file
105
app/Console/Commands/RebuildTrack.php
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2015 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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 Illuminate\Console\Command;
|
||||||
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
|
use Poniverse\Ponyfm\Commands\GenerateTrackFilesCommand;
|
||||||
|
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||||
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
|
|
||||||
|
class RebuildTrack extends Command
|
||||||
|
{
|
||||||
|
use DispatchesJobs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'rebuild:track
|
||||||
|
{trackId : ID of the track to rebuild}
|
||||||
|
{--upload : Include this option to use the uploaded file as the encode source instead of the master file}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Re-encodes a track\'s files';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
/** @var Track $track */
|
||||||
|
$track = Track::with('trackFiles')->withTrashed()->find((int) $this->argument('trackId'));
|
||||||
|
$this->printTrackInfo($track);
|
||||||
|
|
||||||
|
if($this->option('upload')) {
|
||||||
|
// The track would've been deleted if its original upload failed.
|
||||||
|
// It should be restored so the user can publish the track!
|
||||||
|
$track->restore();
|
||||||
|
$this->info("Attempting to finish this track's upload...");
|
||||||
|
|
||||||
|
$sourceFile = new \SplFileInfo($track->getTemporarySourceFile());
|
||||||
|
$generateTrackFiles = new GenerateTrackFilesCommand($track, $sourceFile, false);
|
||||||
|
$result = $generateTrackFiles->execute();
|
||||||
|
// The GenerateTrackFiles command will re-encode all TrackFiles.
|
||||||
|
|
||||||
|
if ($result->didFail()) {
|
||||||
|
$this->error("Something went wrong!");
|
||||||
|
print_r($result->getMessages());
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->info("Re-encoding this track's files - there should be a line of output for each format!");
|
||||||
|
|
||||||
|
foreach ($track->trackFiles as $trackFile) {
|
||||||
|
if (!$trackFile->is_master) {
|
||||||
|
$this->info("Re-encoding this track's {$trackFile->format} file...");
|
||||||
|
$this->dispatch(new EncodeTrackFile($trackFile, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function printTrackInfo(Track $track) {
|
||||||
|
$this->comment("Track info:");
|
||||||
|
$this->comment(" Title: {$track->title}");
|
||||||
|
$this->comment(" Uploaded at: {$track->created_at}");
|
||||||
|
$this->comment(" Artist: {$track->user->display_name} [User ID: {$track->user_id}]");
|
||||||
|
$this->comment(" Artist email: {$track->user->email}");
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,8 +24,8 @@ use File;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Poniverse\Ponyfm\TrackFile;
|
use Poniverse\Ponyfm\Models\TrackFile;
|
||||||
|
|
||||||
class RebuildTrackCache extends Command
|
class RebuildTrackCache extends Command
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Console\Commands;
|
namespace Poniverse\Ponyfm\Console\Commands;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\ResourceLogItem;
|
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||||
use DB;
|
use DB;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
|
|
@ -33,17 +33,18 @@ class Kernel extends ConsoleKernel
|
||||||
protected $commands = [
|
protected $commands = [
|
||||||
\Poniverse\Ponyfm\Console\Commands\MigrateOldData::class,
|
\Poniverse\Ponyfm\Console\Commands\MigrateOldData::class,
|
||||||
\Poniverse\Ponyfm\Console\Commands\RefreshCache::class,
|
\Poniverse\Ponyfm\Console\Commands\RefreshCache::class,
|
||||||
\Poniverse\Ponyfm\Console\Commands\ImportMLPMA::class,
|
|
||||||
\Poniverse\Ponyfm\Console\Commands\ClassifyMLPMA::class,
|
\Poniverse\Ponyfm\Console\Commands\ClassifyMLPMA::class,
|
||||||
\Poniverse\Ponyfm\Console\Commands\PublishUnclassifiedMlpmaTracks::class,
|
|
||||||
\Poniverse\Ponyfm\Console\Commands\RebuildTags::class,
|
\Poniverse\Ponyfm\Console\Commands\RebuildTags::class,
|
||||||
\Poniverse\Ponyfm\Console\Commands\RebuildArtists::class,
|
\Poniverse\Ponyfm\Console\Commands\RebuildArtists::class,
|
||||||
\Poniverse\Ponyfm\Console\Commands\FixYearZeroLogs::class,
|
|
||||||
\Poniverse\Ponyfm\Console\Commands\BootstrapLocalEnvironment::class,
|
\Poniverse\Ponyfm\Console\Commands\BootstrapLocalEnvironment::class,
|
||||||
\Poniverse\Ponyfm\Console\Commands\PoniverseApiSetup::class,
|
\Poniverse\Ponyfm\Console\Commands\PoniverseApiSetup::class,
|
||||||
\Poniverse\Ponyfm\Console\Commands\ClearTrackCache::class,
|
\Poniverse\Ponyfm\Console\Commands\ClearTrackCache::class,
|
||||||
\Poniverse\Ponyfm\Console\Commands\RebuildTrackCache::class,
|
\Poniverse\Ponyfm\Console\Commands\RebuildTrackCache::class,
|
||||||
|
\Poniverse\Ponyfm\Console\Commands\RebuildTrack::class,
|
||||||
\Poniverse\Ponyfm\Console\Commands\RebuildFilesizes::class,
|
\Poniverse\Ponyfm\Console\Commands\RebuildFilesizes::class,
|
||||||
|
\Poniverse\Ponyfm\Console\Commands\RebuildSearchIndex::class,
|
||||||
|
\Poniverse\Ponyfm\Console\Commands\MergeAccounts::class,
|
||||||
|
\Poniverse\Ponyfm\Console\Commands\FixMLPMAImages::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
39
app/Contracts/Searchable.php
Normal file
39
app/Contracts/Searchable.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2016 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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\Contracts;
|
||||||
|
|
||||||
|
interface Searchable {
|
||||||
|
/**
|
||||||
|
* Returns this model in Elasticsearch-friendly form. The array returned by
|
||||||
|
* this method should match the current mapping for this model's ES type.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toElasticsearch():array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool whether this particular object should be indexed or not
|
||||||
|
*/
|
||||||
|
public function shouldBeIndexed():bool;
|
||||||
|
|
||||||
|
public function updateElasticsearchEntry();
|
||||||
|
public function updateElasticsearchEntrySynchronously();
|
||||||
|
}
|
|
@ -22,9 +22,9 @@ namespace Poniverse\Ponyfm\Http\Controllers;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\AlbumDownloader;
|
use Poniverse\Ponyfm\AlbumDownloader;
|
||||||
use App;
|
use App;
|
||||||
use Poniverse\Ponyfm\Album;
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
use Poniverse\Ponyfm\ResourceLogItem;
|
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Illuminate\Support\Facades\Redirect;
|
use Illuminate\Support\Facades\Redirect;
|
||||||
use View;
|
use View;
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Mobile;
|
namespace Poniverse\Ponyfm\Http\Controllers\Api\Mobile;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Http\Controllers\Controller;
|
use Poniverse\Ponyfm\Http\Controllers\Controller;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Response;
|
use Response;
|
||||||
|
|
||||||
class TracksController extends Controller
|
class TracksController extends Controller
|
||||||
|
|
|
@ -22,8 +22,8 @@ namespace Poniverse\Ponyfm\Http\Controllers\Api\V1;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Commands\UploadTrackCommand;
|
use Poniverse\Ponyfm\Commands\UploadTrackCommand;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Poniverse\Ponyfm\Image;
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Response;
|
use Response;
|
||||||
|
|
||||||
class TracksController extends ApiControllerBase
|
class TracksController extends ApiControllerBase
|
||||||
|
|
|
@ -34,6 +34,7 @@ class AccountController extends ApiControllerBase
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
|
|
||||||
return Response::json([
|
return Response::json([
|
||||||
|
'id' => $user->id,
|
||||||
'bio' => $user->bio,
|
'bio' => $user->bio,
|
||||||
'can_see_explicit_content' => $user->can_see_explicit_content == 1,
|
'can_see_explicit_content' => $user->can_see_explicit_content == 1,
|
||||||
'display_name' => $user->display_name,
|
'display_name' => $user->display_name,
|
||||||
|
|
|
@ -21,19 +21,18 @@
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Illuminate\Support\Facades\File;
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
use Poniverse\Ponyfm\Album;
|
|
||||||
use Poniverse\Ponyfm\Commands\CreateAlbumCommand;
|
use Poniverse\Ponyfm\Commands\CreateAlbumCommand;
|
||||||
use Poniverse\Ponyfm\Commands\DeleteAlbumCommand;
|
use Poniverse\Ponyfm\Commands\DeleteAlbumCommand;
|
||||||
use Poniverse\Ponyfm\Commands\EditAlbumCommand;
|
use Poniverse\Ponyfm\Commands\EditAlbumCommand;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Poniverse\Ponyfm\Image;
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||||
use Poniverse\Ponyfm\ResourceLogItem;
|
use Auth;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Input;
|
||||||
use Illuminate\Support\Facades\Input;
|
use Poniverse\Ponyfm\Models\User;
|
||||||
use Illuminate\Support\Facades\Response;
|
use Response;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
|
|
||||||
class AlbumsController extends ApiControllerBase
|
class AlbumsController extends ApiControllerBase
|
||||||
{
|
{
|
||||||
|
@ -142,10 +141,13 @@ class AlbumsController extends ApiControllerBase
|
||||||
200);
|
200);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOwned()
|
public function getOwned(User $user)
|
||||||
{
|
{
|
||||||
$query = Album::summary()->where('user_id', \Auth::user()->id)->orderBy('created_at', 'desc')->get();
|
$this->authorize('get-albums', $user);
|
||||||
|
|
||||||
|
$query = Album::summary()->where('user_id', $user->id)->orderBy('created_at', 'desc')->get();
|
||||||
$albums = [];
|
$albums = [];
|
||||||
|
|
||||||
foreach ($query as $album) {
|
foreach ($query as $album) {
|
||||||
$albums[] = [
|
$albums[] = [
|
||||||
'id' => $album->id,
|
'id' => $album->id,
|
||||||
|
|
|
@ -20,14 +20,14 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Album;
|
use Gate;
|
||||||
use Poniverse\Ponyfm\Comment;
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
use Poniverse\Ponyfm\Favourite;
|
use Poniverse\Ponyfm\Models\Comment;
|
||||||
|
use Poniverse\Ponyfm\Models\Favourite;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Poniverse\Ponyfm\Image;
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Poniverse\Ponyfm\User;
|
use Poniverse\Ponyfm\Models\User;
|
||||||
use Cover;
|
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use Illuminate\Support\Facades\Input;
|
use Illuminate\Support\Facades\Input;
|
||||||
use Illuminate\Support\Facades\Response;
|
use Illuminate\Support\Facades\Response;
|
||||||
|
@ -36,12 +36,13 @@ class ArtistsController extends ApiControllerBase
|
||||||
{
|
{
|
||||||
public function getFavourites($slug)
|
public function getFavourites($slug)
|
||||||
{
|
{
|
||||||
$user = User::whereSlug($slug)->first();
|
$user = User::where('slug', $slug)->whereNull('disabled_at')->first();
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
App::abort(404);
|
App::abort(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$favs = Favourite::whereUserId($user->id)->with([
|
$favs = Favourite::where('user_id', $user->id)
|
||||||
|
->with([
|
||||||
'track.genre',
|
'track.genre',
|
||||||
'track.cover',
|
'track.cover',
|
||||||
'track.user',
|
'track.user',
|
||||||
|
@ -59,10 +60,10 @@ class ArtistsController extends ApiControllerBase
|
||||||
$albums = [];
|
$albums = [];
|
||||||
|
|
||||||
foreach ($favs as $fav) {
|
foreach ($favs as $fav) {
|
||||||
if ($fav->type == 'Poniverse\Ponyfm\Track') {
|
if ($fav->type == 'Poniverse\Ponyfm\Models\Track') {
|
||||||
$tracks[] = Track::mapPublicTrackSummary($fav->track);
|
$tracks[] = Track::mapPublicTrackSummary($fav->track);
|
||||||
} else {
|
} else {
|
||||||
if ($fav->type == 'Poniverse\Ponyfm\Album') {
|
if ($fav->type == 'Poniverse\Ponyfm\Models\Album') {
|
||||||
$albums[] = Album::mapPublicAlbumSummary($fav->album);
|
$albums[] = Album::mapPublicAlbumSummary($fav->album);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,7 +77,7 @@ class ArtistsController extends ApiControllerBase
|
||||||
|
|
||||||
public function getContent($slug)
|
public function getContent($slug)
|
||||||
{
|
{
|
||||||
$user = User::whereSlug($slug)->first();
|
$user = User::where('slug', $slug)->whereNull('disabled_at')->first();
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
App::abort(404);
|
App::abort(404);
|
||||||
}
|
}
|
||||||
|
@ -111,7 +112,8 @@ class ArtistsController extends ApiControllerBase
|
||||||
|
|
||||||
public function getShow($slug)
|
public function getShow($slug)
|
||||||
{
|
{
|
||||||
$user = User::whereSlug($slug)
|
$user = User::where('slug', $slug)
|
||||||
|
->whereNull('disabled_at')
|
||||||
->userDetails()
|
->userDetails()
|
||||||
->with([
|
->with([
|
||||||
'comments' => function ($query) {
|
'comments' => function ($query) {
|
||||||
|
@ -157,7 +159,7 @@ class ArtistsController extends ApiControllerBase
|
||||||
|
|
||||||
return Response::json([
|
return Response::json([
|
||||||
'artist' => [
|
'artist' => [
|
||||||
'id' => (int)$user->id,
|
'id' => $user->id,
|
||||||
'name' => $user->display_name,
|
'name' => $user->display_name,
|
||||||
'slug' => $user->slug,
|
'slug' => $user->slug,
|
||||||
'is_archived' => (bool)$user->is_archived,
|
'is_archived' => (bool)$user->is_archived,
|
||||||
|
@ -173,7 +175,10 @@ class ArtistsController extends ApiControllerBase
|
||||||
'bio' => $user->bio,
|
'bio' => $user->bio,
|
||||||
'mlpforums_username' => $user->username,
|
'mlpforums_username' => $user->username,
|
||||||
'message_url' => $user->message_url,
|
'message_url' => $user->message_url,
|
||||||
'user_data' => $userData
|
'user_data' => $userData,
|
||||||
|
'permissions' => [
|
||||||
|
'edit' => Gate::allows('edit', $user)
|
||||||
|
]
|
||||||
]
|
]
|
||||||
], 200);
|
], 200);
|
||||||
}
|
}
|
||||||
|
@ -195,18 +200,7 @@ class ArtistsController extends ApiControllerBase
|
||||||
$users = [];
|
$users = [];
|
||||||
|
|
||||||
foreach ($query->get() as $user) {
|
foreach ($query->get() as $user) {
|
||||||
$users[] = [
|
$users[] = User::mapPublicUserSummary($user);
|
||||||
'id' => $user->id,
|
|
||||||
'name' => $user->display_name,
|
|
||||||
'slug' => $user->slug,
|
|
||||||
'url' => $user->url,
|
|
||||||
'is_archived' => $user->is_archived,
|
|
||||||
'avatars' => [
|
|
||||||
'small' => $user->getAvatarUrl(Image::SMALL),
|
|
||||||
'normal' => $user->getAvatarUrl(Image::NORMAL)
|
|
||||||
],
|
|
||||||
'created_at' => $user->created_at
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response::json(["artists" => $users, "current_page" => $page, "total_pages" => ceil($count / $perPage)],
|
return Response::json(["artists" => $users, "current_page" => $page, "total_pages" => ceil($count / $perPage)],
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
use App;
|
use App;
|
||||||
use Poniverse\Ponyfm\Commands\CreateCommentCommand;
|
use Poniverse\Ponyfm\Commands\CreateCommentCommand;
|
||||||
use Poniverse\Ponyfm\Comment;
|
use Poniverse\Ponyfm\Models\Comment;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Illuminate\Support\Facades\Input;
|
use Illuminate\Support\Facades\Input;
|
||||||
use Illuminate\Support\Facades\Response;
|
use Illuminate\Support\Facades\Response;
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Input;
|
use Illuminate\Support\Facades\Input;
|
||||||
use Illuminate\Support\Facades\Response;
|
use Illuminate\Support\Facades\Response;
|
||||||
|
|
|
@ -20,12 +20,12 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Album;
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
use Poniverse\Ponyfm\Commands\ToggleFavouriteCommand;
|
use Poniverse\Ponyfm\Commands\ToggleFavouriteCommand;
|
||||||
use Poniverse\Ponyfm\Favourite;
|
use Poniverse\Ponyfm\Models\Favourite;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Poniverse\Ponyfm\Playlist;
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Input;
|
use Illuminate\Support\Facades\Input;
|
||||||
use Illuminate\Support\Facades\Response;
|
use Illuminate\Support\Facades\Response;
|
||||||
|
|
|
@ -21,9 +21,10 @@
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
use Input;
|
use Input;
|
||||||
|
use Poniverse\Ponyfm\Commands\CreateGenreCommand;
|
||||||
use Poniverse\Ponyfm\Commands\DeleteGenreCommand;
|
use Poniverse\Ponyfm\Commands\DeleteGenreCommand;
|
||||||
use Poniverse\Ponyfm\Commands\RenameGenreCommand;
|
use Poniverse\Ponyfm\Commands\RenameGenreCommand;
|
||||||
use Poniverse\Ponyfm\Genre;
|
use Poniverse\Ponyfm\Models\Genre;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Response;
|
use Response;
|
||||||
|
|
||||||
|
@ -45,6 +46,11 @@ class GenresController extends ApiControllerBase
|
||||||
], 200);
|
], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function postCreate()
|
||||||
|
{
|
||||||
|
$command = new CreateGenreCommand(Input::get('name'));
|
||||||
|
return $this->execute($command);
|
||||||
|
}
|
||||||
|
|
||||||
public function putRename($genreId)
|
public function putRename($genreId)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,17 +20,21 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
|
use Auth;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Poniverse\Ponyfm\Image;
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
use Cover;
|
use Poniverse\Ponyfm\Models\User;
|
||||||
use Illuminate\Support\Facades\Response;
|
use Response;
|
||||||
|
|
||||||
class ImagesController extends ApiControllerBase
|
class ImagesController extends ApiControllerBase
|
||||||
{
|
{
|
||||||
public function getOwned()
|
public function getOwned(User $user)
|
||||||
{
|
{
|
||||||
$query = Image::where('uploaded_by', \Auth::user()->id);
|
$this->authorize('get-images', $user);
|
||||||
|
|
||||||
|
$query = Image::where('uploaded_by', $user->id);
|
||||||
$images = [];
|
$images = [];
|
||||||
|
|
||||||
foreach ($query->get() as $image) {
|
foreach ($query->get() as $image) {
|
||||||
$images[] = [
|
$images[] = [
|
||||||
'id' => $image->id,
|
'id' => $image->id,
|
||||||
|
|
|
@ -27,13 +27,13 @@ use Poniverse\Ponyfm\Commands\DeletePlaylistCommand;
|
||||||
use Poniverse\Ponyfm\Commands\EditPlaylistCommand;
|
use Poniverse\Ponyfm\Commands\EditPlaylistCommand;
|
||||||
use Poniverse\Ponyfm\Commands\RemoveTrackFromPlaylistCommand;
|
use Poniverse\Ponyfm\Commands\RemoveTrackFromPlaylistCommand;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Poniverse\Ponyfm\Image;
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
use Poniverse\Ponyfm\Playlist;
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
use Poniverse\Ponyfm\ResourceLogItem;
|
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Auth;
|
||||||
use Illuminate\Support\Facades\Input;
|
use Input;
|
||||||
use Illuminate\Support\Facades\Response;
|
use Response;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
|
|
||||||
class PlaylistsController extends ApiControllerBase
|
class PlaylistsController extends ApiControllerBase
|
||||||
{
|
{
|
||||||
|
@ -175,8 +175,12 @@ class PlaylistsController extends ApiControllerBase
|
||||||
|
|
||||||
public function getOwned()
|
public function getOwned()
|
||||||
{
|
{
|
||||||
$query = Playlist::summary()->with('pins', 'tracks', 'tracks.cover')->where('user_id',
|
$query = Playlist::summary()
|
||||||
\Auth::user()->id)->orderBy('title', 'asc')->get();
|
->with('pins', 'tracks', 'tracks.cover')
|
||||||
|
->where('user_id', Auth::user()->id)
|
||||||
|
->orderBy('title', 'asc')
|
||||||
|
->get();
|
||||||
|
|
||||||
$playlists = [];
|
$playlists = [];
|
||||||
foreach ($query as $playlist) {
|
foreach ($query as $playlist) {
|
||||||
$playlists[] = [
|
$playlists[] = [
|
||||||
|
@ -191,7 +195,8 @@ class PlaylistsController extends ApiControllerBase
|
||||||
'normal' => $playlist->getCoverUrl(Image::NORMAL)
|
'normal' => $playlist->getCoverUrl(Image::NORMAL)
|
||||||
],
|
],
|
||||||
'is_pinned' => $playlist->hasPinFor(Auth::user()->id),
|
'is_pinned' => $playlist->hasPinFor(Auth::user()->id),
|
||||||
'is_public' => $playlist->is_public == 1
|
'is_public' => $playlist->is_public == 1,
|
||||||
|
'track_ids' => $playlist->tracks->pluck('id')
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pony.fm - A community for pony fan music.
|
* Pony.fm - A community for pony fan music.
|
||||||
* Copyright (C) 2015 Peter Deltchev
|
* Copyright (C) 2016 Peter Deltchev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
@ -20,28 +20,20 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Http\Controllers\Controller;
|
use Elasticsearch;
|
||||||
use Poniverse\Ponyfm\ProfileRequest;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Cache;
|
use Input;
|
||||||
use Config;
|
use Poniverse\Ponyfm\Library\Search;
|
||||||
use Response;
|
use Response;
|
||||||
|
|
||||||
class ProfilerController extends Controller
|
class SearchController extends ApiControllerBase
|
||||||
{
|
{
|
||||||
public function getRequest($id)
|
public function getSearch(Search $search)
|
||||||
{
|
{
|
||||||
if (!Config::get('app.debug')) {
|
$results = $search->searchAllContent(Input::query('query'));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$key = 'profiler-request-' . $id;
|
return Response::json([
|
||||||
$request = Cache::get($key);
|
'results' => $results,
|
||||||
if (!$request) {
|
], 200);
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
Cache::forget($key);
|
|
||||||
|
|
||||||
return Response::json(['request' => ProfileRequest::load($request)->toArray()], 200);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -20,11 +20,11 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Genre;
|
use Poniverse\Ponyfm\Models\Genre;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Poniverse\Ponyfm\License;
|
use Poniverse\Ponyfm\Models\License;
|
||||||
use Poniverse\Ponyfm\ShowSong;
|
use Poniverse\Ponyfm\Models\ShowSong;
|
||||||
use Poniverse\Ponyfm\TrackType;
|
use Poniverse\Ponyfm\Models\TrackType;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class TaxonomiesController extends ApiControllerBase
|
class TaxonomiesController extends ApiControllerBase
|
||||||
|
@ -33,8 +33,10 @@ class TaxonomiesController extends ApiControllerBase
|
||||||
{
|
{
|
||||||
return \Response::json([
|
return \Response::json([
|
||||||
'licenses' => License::all()->toArray(),
|
'licenses' => License::all()->toArray(),
|
||||||
'genres' => Genre::select('genres.*',
|
'genres' => Genre::with('trackCountRelation')
|
||||||
DB::raw('(SELECT COUNT(id) FROM tracks WHERE tracks.genre_id = genres.id AND tracks.published_at IS NOT NULL) AS track_count'))->orderBy('name')->get()->toArray(),
|
->orderBy('name')
|
||||||
|
->get()
|
||||||
|
->toArray(),
|
||||||
'track_types' => TrackType::select('track_types.*',
|
'track_types' => TrackType::select('track_types.*',
|
||||||
DB::raw('(SELECT COUNT(id) FROM tracks WHERE tracks.track_type_id = track_types.id AND tracks.published_at IS NOT NULL) AS track_count'))
|
DB::raw('(SELECT COUNT(id) FROM tracks WHERE tracks.track_type_id = track_types.id AND tracks.published_at IS NOT NULL) AS track_count'))
|
||||||
->where('id', '!=', TrackType::UNCLASSIFIED_TRACK)
|
->where('id', '!=', TrackType::UNCLASSIFIED_TRACK)
|
||||||
|
|
|
@ -22,15 +22,14 @@ namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use File;
|
use File;
|
||||||
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
|
|
||||||
use Poniverse\Ponyfm\Commands\DeleteTrackCommand;
|
use Poniverse\Ponyfm\Commands\DeleteTrackCommand;
|
||||||
use Poniverse\Ponyfm\Commands\EditTrackCommand;
|
use Poniverse\Ponyfm\Commands\EditTrackCommand;
|
||||||
use Poniverse\Ponyfm\Commands\UploadTrackCommand;
|
use Poniverse\Ponyfm\Commands\UploadTrackCommand;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||||
use Poniverse\Ponyfm\ResourceLogItem;
|
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||||
use Poniverse\Ponyfm\TrackFile;
|
use Poniverse\Ponyfm\Models\TrackFile;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Input;
|
use Input;
|
||||||
use Response;
|
use Response;
|
||||||
|
@ -41,7 +40,7 @@ class TracksController extends ApiControllerBase
|
||||||
{
|
{
|
||||||
session_write_close();
|
session_write_close();
|
||||||
|
|
||||||
return $this->execute(new UploadTrackCommand());
|
return $this->execute(new UploadTrackCommand(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUploadStatus($trackId)
|
public function getUploadStatus($trackId)
|
||||||
|
@ -184,9 +183,7 @@ class TracksController extends ApiControllerBase
|
||||||
return $this->notFound('Track ' . $id . ' not found!');
|
return $this->notFound('Track ' . $id . ' not found!');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($track->user_id != Auth::user()->id) {
|
$this->authorize('edit', $track);
|
||||||
return $this->notAuthorized();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response::json(Track::mapPrivateTrackShow($track), 200);
|
return Response::json(Track::mapPrivateTrackShow($track), 200);
|
||||||
}
|
}
|
||||||
|
@ -225,6 +222,9 @@ class TracksController extends ApiControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Input::has('songs')) {
|
if (Input::has('songs')) {
|
||||||
|
// DISTINCT is needed here to avoid duplicate results
|
||||||
|
// when a track is associated with multiple show songs.
|
||||||
|
$query->distinct();
|
||||||
$query->join('show_song_track', function ($join) {
|
$query->join('show_song_track', function ($join) {
|
||||||
$join->on('tracks.id', '=', 'show_song_track.track_id');
|
$join->on('tracks.id', '=', 'show_song_track.track_id');
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers;
|
namespace Poniverse\Ponyfm\Http\Controllers;
|
||||||
|
|
||||||
use App;
|
use App;
|
||||||
use Poniverse\Ponyfm\User;
|
use Poniverse\Ponyfm\Models\User;
|
||||||
use View;
|
use View;
|
||||||
use Redirect;
|
use Redirect;
|
||||||
|
|
||||||
|
@ -32,9 +32,17 @@ class ArtistsController extends Controller
|
||||||
return View::make('artists.index');
|
return View::make('artists.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFavourites($slug) {
|
||||||
|
return $this->getProfile($slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent($slug) {
|
||||||
|
return $this->getProfile($slug);
|
||||||
|
}
|
||||||
|
|
||||||
public function getProfile($slug)
|
public function getProfile($slug)
|
||||||
{
|
{
|
||||||
$user = User::whereSlug($slug)->first();
|
$user = User::whereSlug($slug)->whereNull('disabled_at')->first();
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
App::abort('404');
|
App::abort('404');
|
||||||
}
|
}
|
||||||
|
@ -45,10 +53,10 @@ class ArtistsController extends Controller
|
||||||
public function getShortlink($id)
|
public function getShortlink($id)
|
||||||
{
|
{
|
||||||
$user = User::find($id);
|
$user = User::find($id);
|
||||||
if (!$user) {
|
if (!$user || $user->disabled_at !== NULL) {
|
||||||
App::abort('404');
|
App::abort('404');
|
||||||
}
|
}
|
||||||
|
|
||||||
return Redirect::action('ArtistsController@getProfile', [$id]);
|
return Redirect::action('ArtistsController@getProfile', [$user->slug]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers;
|
namespace Poniverse\Ponyfm\Http\Controllers;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\User;
|
use Poniverse\Ponyfm\Models\User;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Config;
|
use Config;
|
||||||
use DB;
|
use DB;
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers;
|
namespace Poniverse\Ponyfm\Http\Controllers;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\Image;
|
use Poniverse\Ponyfm\Models\Image;
|
||||||
use Config;
|
use Config;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use Illuminate\Support\Facades\Redirect;
|
use Illuminate\Support\Facades\Redirect;
|
||||||
|
|
|
@ -21,9 +21,9 @@
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers;
|
namespace Poniverse\Ponyfm\Http\Controllers;
|
||||||
|
|
||||||
use App;
|
use App;
|
||||||
use Poniverse\Ponyfm\Playlist;
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
use Poniverse\Ponyfm\ResourceLogItem;
|
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Poniverse\Ponyfm\PlaylistDownloader;
|
use Poniverse\Ponyfm\PlaylistDownloader;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Illuminate\Support\Facades\Redirect;
|
use Illuminate\Support\Facades\Redirect;
|
||||||
|
|
|
@ -20,9 +20,9 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers;
|
namespace Poniverse\Ponyfm\Http\Controllers;
|
||||||
|
|
||||||
use Poniverse\Ponyfm\ResourceLogItem;
|
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Poniverse\Ponyfm\TrackFile;
|
use Poniverse\Ponyfm\Models\TrackFile;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Config;
|
use Config;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
|
@ -91,6 +91,11 @@ class TracksController extends Controller
|
||||||
return View::make('tracks.show');
|
return View::make('tracks.show');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEdit($id, $slug)
|
||||||
|
{
|
||||||
|
return $this->getTrack($id, $slug);
|
||||||
|
}
|
||||||
|
|
||||||
public function getShortlink($id)
|
public function getShortlink($id)
|
||||||
{
|
{
|
||||||
$track = Track::find($id);
|
$track = Track::find($id);
|
||||||
|
|
|
@ -36,7 +36,7 @@ class Kernel extends HttpKernel
|
||||||
\Illuminate\Session\Middleware\StartSession::class,
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||||
\Poniverse\Ponyfm\Http\Middleware\VerifyCsrfToken::class,
|
\Poniverse\Ponyfm\Http\Middleware\VerifyCsrfToken::class,
|
||||||
\Poniverse\Ponyfm\Http\Middleware\Profiler::class,
|
\Poniverse\Ponyfm\Http\Middleware\DisabledAccountCheck::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,11 +20,13 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Http\Middleware;
|
namespace Poniverse\Ponyfm\Http\Middleware;
|
||||||
|
|
||||||
use Auth;
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use GuzzleHttp;
|
use GuzzleHttp;
|
||||||
|
use Illuminate\Auth\Guard;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Session\Store;
|
||||||
use Poniverse;
|
use Poniverse;
|
||||||
use Poniverse\Ponyfm\User;
|
use Poniverse\Ponyfm\Models\User;
|
||||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
|
||||||
class AuthenticateOAuth
|
class AuthenticateOAuth
|
||||||
|
@ -34,8 +36,20 @@ class AuthenticateOAuth
|
||||||
*/
|
*/
|
||||||
private $poniverse;
|
private $poniverse;
|
||||||
|
|
||||||
public function __construct(Poniverse $poniverse) {
|
/**
|
||||||
|
* @var Guard
|
||||||
|
*/
|
||||||
|
private $auth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Store
|
||||||
|
*/
|
||||||
|
private $session;
|
||||||
|
|
||||||
|
public function __construct(Poniverse $poniverse, Guard $auth, Store $session) {
|
||||||
$this->poniverse = $poniverse;
|
$this->poniverse = $poniverse;
|
||||||
|
$this->auth = $auth;
|
||||||
|
$this->session = $session;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,10 +61,10 @@ class AuthenticateOAuth
|
||||||
* @return mixed
|
* @return mixed
|
||||||
* @throws \OAuth2\Exception
|
* @throws \OAuth2\Exception
|
||||||
*/
|
*/
|
||||||
public function handle($request, Closure $next, $requiredScope)
|
public function handle(Request $request, Closure $next, $requiredScope)
|
||||||
{
|
{
|
||||||
// Ensure this is a valid OAuth client.
|
// Ensure this is a valid OAuth client.
|
||||||
$accessToken = $request->get('access_token');
|
$accessToken = $this->determineAccessToken($request, false);
|
||||||
|
|
||||||
// check that access token is valid at Poniverse.net
|
// check that access token is valid at Poniverse.net
|
||||||
$accessTokenInfo = $this->poniverse->getAccessTokenInfo($accessToken);
|
$accessTokenInfo = $this->poniverse->getAccessTokenInfo($accessToken);
|
||||||
|
@ -65,13 +79,29 @@ class AuthenticateOAuth
|
||||||
|
|
||||||
// Log in as the given user, creating the account if necessary.
|
// Log in as the given user, creating the account if necessary.
|
||||||
$this->poniverse->setAccessToken($accessToken);
|
$this->poniverse->setAccessToken($accessToken);
|
||||||
session()->put('api_client_id', $accessTokenInfo->getClientId());
|
$this->session->put('api_client_id', $accessTokenInfo->getClientId());
|
||||||
|
|
||||||
$poniverseUser = $this->poniverse->getUser();
|
$poniverseUser = $this->poniverse->getUser();
|
||||||
|
|
||||||
$user = User::findOrCreate($poniverseUser['username'], $poniverseUser['display_name'], $poniverseUser['email']);
|
$user = User::findOrCreate($poniverseUser['username'], $poniverseUser['display_name'], $poniverseUser['email']);
|
||||||
Auth::login($user);
|
$this->auth->onceUsingId($user);
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function determineAccessToken(Request $request, $headerOnly = true)
|
||||||
|
{
|
||||||
|
$header = $request->header('Authorization');
|
||||||
|
|
||||||
|
if ($header !== null && substr($header, 0, 7) === 'Bearer ') {
|
||||||
|
return trim(substr($header, 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($headerOnly) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request->get('access_token');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
68
app/Http/Middleware/DisabledAccountCheck.php
Normal file
68
app/Http/Middleware/DisabledAccountCheck.php
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2015 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\Auth\Guard;
|
||||||
|
|
||||||
|
class DisabledAccountCheck
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The Guard implementation.
|
||||||
|
*
|
||||||
|
* @var Guard
|
||||||
|
*/
|
||||||
|
protected $auth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new filter instance.
|
||||||
|
*
|
||||||
|
* @param Guard $auth
|
||||||
|
*/
|
||||||
|
public function __construct(Guard $auth) {
|
||||||
|
$this->auth = $auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure $next
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle($request, Closure $next)
|
||||||
|
{
|
||||||
|
// TODO: don't automatically log the user out some time after
|
||||||
|
// issue #29 is fixed or when disabled_at starts being used for
|
||||||
|
// something other than merged accounts.
|
||||||
|
if ($this->auth->check()
|
||||||
|
&& $this->auth->user()->disabled_at !== null
|
||||||
|
&& !($request->getMethod() === 'POST' && $request->getRequestUri() == '/auth/logout')
|
||||||
|
) {
|
||||||
|
$this->auth->logout();
|
||||||
|
// return Response::view('home.account-disabled', ['username' => $this->auth->user()->username], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,80 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pony.fm - A community for pony fan music.
|
|
||||||
* Copyright (C) 2015 Peter Deltchev
|
|
||||||
*
|
|
||||||
* 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\Http\Middleware;
|
|
||||||
|
|
||||||
use App;
|
|
||||||
use Closure;
|
|
||||||
use Cache;
|
|
||||||
use Config;
|
|
||||||
use DB;
|
|
||||||
use Log;
|
|
||||||
use Poniverse\Ponyfm\ProfileRequest;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
|
|
||||||
class Profiler
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Handle an incoming request.
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure $next
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle($request, Closure $next)
|
|
||||||
{
|
|
||||||
// Profiling magic time!
|
|
||||||
if (Config::get('app.debug')) {
|
|
||||||
DB::enableQueryLog();
|
|
||||||
$profiler = ProfileRequest::create();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$response = $next($request);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$response = \Response::make([
|
|
||||||
'message' => $e->getMessage(),
|
|
||||||
'lineNumber' => $e->getLine(),
|
|
||||||
'exception' => $e->getTrace()
|
|
||||||
], method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500);
|
|
||||||
$profiler->log('error', $e->__toString(), []);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = $this->processResponse($profiler, $response);
|
|
||||||
|
|
||||||
Log::listen(function ($level, $message, $context) use ($profiler, $request) {
|
|
||||||
$profiler->log($level, $message, $context);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Process the request the usual, boring way.
|
|
||||||
$response = $next($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected function processResponse(ProfileRequest $profiler, Response $response) {
|
|
||||||
$profiler->recordQueries();
|
|
||||||
|
|
||||||
Cache::put('profiler-request-' . $profiler->getId(), $profiler->toString(), 2);
|
|
||||||
return $response->header('X-Request-Id', $profiler->getId());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,16 +29,13 @@
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (Config::get('app.debug')) {
|
|
||||||
Route::get('/api/web/profiler/{id}', 'Api\Web\ProfilerController@getRequest');
|
|
||||||
}
|
|
||||||
|
|
||||||
Route::get('/dashboard', 'TracksController@getIndex');
|
Route::get('/dashboard', 'TracksController@getIndex');
|
||||||
Route::get('/tracks', ['as' => 'tracks.discover', 'uses' => 'TracksController@getIndex']);
|
Route::get('/tracks', ['as' => 'tracks.discover', 'uses' => 'TracksController@getIndex']);
|
||||||
Route::get('/tracks/popular', 'TracksController@getIndex');
|
Route::get('/tracks/popular', 'TracksController@getIndex');
|
||||||
Route::get('/tracks/random', 'TracksController@getIndex');
|
Route::get('/tracks/random', 'TracksController@getIndex');
|
||||||
|
|
||||||
Route::get('tracks/{id}-{slug}', 'TracksController@getTrack');
|
Route::get('tracks/{id}-{slug}', 'TracksController@getTrack');
|
||||||
|
Route::get('tracks/{id}-{slug}/edit', 'TracksController@getEdit');
|
||||||
Route::get('t{id}', 'TracksController@getShortlink' )->where('id', '\d+');
|
Route::get('t{id}', 'TracksController@getShortlink' )->where('id', '\d+');
|
||||||
Route::get('t{id}/embed', 'TracksController@getEmbed' );
|
Route::get('t{id}/embed', 'TracksController@getEmbed' );
|
||||||
Route::get('t{id}/stream.{extension}', 'TracksController@getStream' );
|
Route::get('t{id}/stream.{extension}', 'TracksController@getStream' );
|
||||||
|
@ -54,6 +51,7 @@ Route::get('playlists', 'PlaylistsController@getIndex');
|
||||||
|
|
||||||
Route::get('/register', 'AccountController@getRegister');
|
Route::get('/register', 'AccountController@getRegister');
|
||||||
Route::get('/login', 'AuthController@getLogin');
|
Route::get('/login', 'AuthController@getLogin');
|
||||||
|
Route::post('/auth/logout', 'AuthController@postLogout');
|
||||||
Route::get('/auth/oauth', 'AuthController@getOAuth');
|
Route::get('/auth/oauth', 'AuthController@getOAuth');
|
||||||
|
|
||||||
Route::get('/about', function() { return View::make('pages.about'); });
|
Route::get('/about', function() { return View::make('pages.about'); });
|
||||||
|
@ -81,8 +79,7 @@ Route::group(['prefix' => 'api/v1', 'middleware' => 'json-exceptions'], function
|
||||||
|
|
||||||
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('/search', 'Api\Web\SearchController@getSearch');
|
||||||
Route::get('/playlists/show/{id}', 'Api\Web\PlaylistsController@getShow');
|
|
||||||
|
|
||||||
Route::get('/tracks', 'Api\Web\TracksController@getIndex');
|
Route::get('/tracks', 'Api\Web\TracksController@getIndex');
|
||||||
Route::get('/tracks/{id}', 'Api\Web\TracksController@getShow')->where('id', '\d+');
|
Route::get('/tracks/{id}', 'Api\Web\TracksController@getShow')->where('id', '\d+');
|
||||||
|
@ -93,6 +90,7 @@ Route::group(['prefix' => 'api/web'], function() {
|
||||||
Route::get('/albums/cached/{id}/{format}', 'Api\Web\AlbumsController@getCachedAlbum')->where(['id' => '\d+', 'format' => '.+']);
|
Route::get('/albums/cached/{id}/{format}', 'Api\Web\AlbumsController@getCachedAlbum')->where(['id' => '\d+', 'format' => '.+']);
|
||||||
|
|
||||||
Route::get('/playlists', 'Api\Web\PlaylistsController@getIndex');
|
Route::get('/playlists', 'Api\Web\PlaylistsController@getIndex');
|
||||||
|
Route::get('/playlists/show/{id}', 'Api\Web\PlaylistsController@getShow');
|
||||||
Route::get('/playlists/{id}', 'Api\Web\PlaylistsController@getShow')->where('id', '\d+');
|
Route::get('/playlists/{id}', 'Api\Web\PlaylistsController@getShow')->where('id', '\d+');
|
||||||
Route::get('/playlists/cached/{id}/{format}', 'Api\Web\PlaylistsController@getCachedPlaylist')->where(['id' => '\d+', 'format' => '.+']);
|
Route::get('/playlists/cached/{id}/{format}', 'Api\Web\PlaylistsController@getCachedPlaylist')->where(['id' => '\d+', 'format' => '.+']);
|
||||||
|
|
||||||
|
@ -135,12 +133,12 @@ Route::group(['prefix' => 'api/web'], function() {
|
||||||
Route::group(['middleware' => 'auth'], function() {
|
Route::group(['middleware' => 'auth'], function() {
|
||||||
Route::get('/account/settings', 'Api\Web\AccountController@getSettings');
|
Route::get('/account/settings', 'Api\Web\AccountController@getSettings');
|
||||||
|
|
||||||
Route::get('/images/owned', 'Api\Web\ImagesController@getOwned');
|
|
||||||
|
|
||||||
Route::get('/tracks/owned', 'Api\Web\TracksController@getOwned');
|
Route::get('/tracks/owned', 'Api\Web\TracksController@getOwned');
|
||||||
Route::get('/tracks/edit/{id}', 'Api\Web\TracksController@getEdit');
|
Route::get('/tracks/edit/{id}', 'Api\Web\TracksController@getEdit');
|
||||||
|
|
||||||
Route::get('/albums/owned', 'Api\Web\AlbumsController@getOwned');
|
Route::get('/users/{userId}/albums', 'Api\Web\AlbumsController@getOwned')->where('id', '\d+');
|
||||||
|
Route::get('/users/{userId}/images', 'Api\Web\ImagesController@getOwned')->where('id', '\d+');
|
||||||
|
|
||||||
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/owned', 'Api\Web\PlaylistsController@getOwned');
|
||||||
|
@ -153,6 +151,7 @@ Route::group(['prefix' => 'api/web'], function() {
|
||||||
|
|
||||||
Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'can:access-admin-area']], function() {
|
Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'can:access-admin-area']], function() {
|
||||||
Route::get('/genres', 'Api\Web\GenresController@getIndex');
|
Route::get('/genres', 'Api\Web\GenresController@getIndex');
|
||||||
|
Route::post('/genres', 'Api\Web\GenresController@postCreate');
|
||||||
Route::put('/genres/{id}', 'Api\Web\GenresController@putRename')->where('id', '\d+');
|
Route::put('/genres/{id}', 'Api\Web\GenresController@putRename')->where('id', '\d+');
|
||||||
Route::delete('/genres/{id}', 'Api\Web\GenresController@deleteGenre')->where('id', '\d+');
|
Route::delete('/genres/{id}', 'Api\Web\GenresController@deleteGenre')->where('id', '\d+');
|
||||||
});
|
});
|
||||||
|
@ -160,11 +159,23 @@ Route::group(['prefix' => 'api/web'], function() {
|
||||||
Route::post('/auth/logout', 'Api\Web\AuthController@postLogout');
|
Route::post('/auth/logout', 'Api\Web\AuthController@postLogout');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'account', 'middleware' => 'auth'], function() {
|
|
||||||
Route::get('/favourites/tracks', 'FavouritesController@getTracks');
|
|
||||||
Route::get('/favourites/albums', 'FavouritesController@getAlbums');
|
|
||||||
Route::get('/favourites/playlists', 'FavouritesController@getPlaylists');
|
|
||||||
|
|
||||||
|
Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'can:access-admin-area']], function() {
|
||||||
|
Route::get('/genres', 'AdminController@getGenres');
|
||||||
|
Route::get('/', 'AdminController@getIndex');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::get('u{id}', 'ArtistsController@getShortlink')->where('id', '\d+');
|
||||||
|
Route::get('users/{id}-{slug}', 'ArtistsController@getShortlink')->where('id', '\d+');
|
||||||
|
|
||||||
|
|
||||||
|
Route::group(['prefix' => '{slug}'], function() {
|
||||||
|
Route::get('/', 'ArtistsController@getProfile');
|
||||||
|
Route::get('/content', 'ArtistsController@getContent');
|
||||||
|
Route::get('/favourites', 'ArtistsController@getFavourites');
|
||||||
|
|
||||||
|
|
||||||
|
Route::group(['prefix' => 'account', 'middleware' => 'auth'], function() {
|
||||||
Route::get('/tracks', 'ContentController@getTracks');
|
Route::get('/tracks', 'ContentController@getTracks');
|
||||||
Route::get('/tracks/edit/{id}', 'ContentController@getTracks');
|
Route::get('/tracks/edit/{id}', 'ContentController@getTracks');
|
||||||
Route::get('/albums', 'ContentController@getAlbums');
|
Route::get('/albums', 'ContentController@getAlbums');
|
||||||
|
@ -176,18 +187,8 @@ Route::group(['prefix' => 'account', 'middleware' => 'auth'], function() {
|
||||||
|
|
||||||
Route::get('/', 'AccountController@getIndex');
|
Route::get('/', 'AccountController@getIndex');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'can:access-admin-area']], function() {
|
|
||||||
Route::get('/genres', 'AdminController@getGenres');
|
|
||||||
Route::get('/', 'AdminController@getIndex');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('u{id}', 'ArtistsController@getShortlink')->where('id', '\d+');
|
|
||||||
Route::get('users/{id}-{slug}', 'ArtistsController@getShortlink')->where('id', '\d+');
|
|
||||||
Route::get('{slug}', 'ArtistsController@getProfile');
|
|
||||||
Route::get('{slug}/content', 'ArtistsController@getProfile');
|
|
||||||
Route::get('{slug}/favourites', 'ArtistsController@getProfile');
|
|
||||||
|
|
||||||
Route::get('/', 'HomeController@getIndex');
|
Route::get('/', 'HomeController@getIndex');
|
||||||
|
|
||||||
Route::group(['domain' => 'api.pony.fm'], function() {
|
Route::group(['domain' => 'api.pony.fm'], function() {
|
||||||
|
|
|
@ -21,11 +21,12 @@
|
||||||
namespace Poniverse\Ponyfm\Jobs;
|
namespace Poniverse\Ponyfm\Jobs;
|
||||||
|
|
||||||
use Auth;
|
use Auth;
|
||||||
use Poniverse\Ponyfm\Genre;
|
use DB;
|
||||||
|
use Poniverse\Ponyfm\Models\Genre;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Contracts\Bus\SelfHandling;
|
use Illuminate\Contracts\Bus\SelfHandling;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use SerializesModels;
|
use SerializesModels;
|
||||||
|
|
||||||
class DeleteGenre extends Job implements SelfHandling, ShouldQueue
|
class DeleteGenre extends Job implements SelfHandling, ShouldQueue
|
||||||
|
@ -60,6 +61,8 @@ class DeleteGenre extends Job implements SelfHandling, ShouldQueue
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$this->beforeHandle();
|
||||||
|
|
||||||
// The user who kicked off this job is used when generating revision log entries.
|
// The user who kicked off this job is used when generating revision log entries.
|
||||||
Auth::login($this->executingUser);
|
Auth::login($this->executingUser);
|
||||||
|
|
||||||
|
|
|
@ -24,17 +24,15 @@ namespace Poniverse\Ponyfm\Jobs;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use DB;
|
use DB;
|
||||||
use File;
|
use File;
|
||||||
use Illuminate\Support\Facades\Config;
|
use Config;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Log;
|
||||||
use OAuth2\Exception;
|
|
||||||
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
|
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
|
||||||
use Poniverse\Ponyfm\Jobs\Job;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Contracts\Bus\SelfHandling;
|
use Illuminate\Contracts\Bus\SelfHandling;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
use Poniverse\Ponyfm\TrackFile;
|
use Poniverse\Ponyfm\Models\TrackFile;
|
||||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||||
use Symfony\Component\Process\Process;
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
@ -44,19 +42,19 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
||||||
/**
|
/**
|
||||||
* @var TrackFile
|
* @var TrackFile
|
||||||
*/
|
*/
|
||||||
private $trackFile;
|
protected $trackFile;
|
||||||
/**
|
/**
|
||||||
* @var
|
* @var
|
||||||
*/
|
*/
|
||||||
private $isExpirable;
|
protected $isExpirable;
|
||||||
/**
|
/**
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
private $isForUpload;
|
protected $isForUpload;
|
||||||
/**
|
/**
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
private $autoPublishWhenComplete;
|
protected $autoPublishWhenComplete;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
|
@ -87,9 +85,21 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$this->beforeHandle();
|
||||||
|
|
||||||
|
// Sanity check: was this file just generated, or is it already being processed?
|
||||||
|
if ($this->trackFile->status === TrackFile::STATUS_PROCESSING) {
|
||||||
|
Log::warning('Track file #'.$this->trackFile->id.' (track #'.$this->trackFile->track_id.') is already being processed!');
|
||||||
|
return;
|
||||||
|
|
||||||
|
} elseif (!$this->trackFile->is_expired) {
|
||||||
|
Log::warning('Track file #'.$this->trackFile->id.' (track #'.$this->trackFile->track_id.') is still valid! No need to re-encode it.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Start the job
|
// Start the job
|
||||||
$this->trackFile->status = TrackFile::STATUS_PROCESSING;
|
$this->trackFile->status = TrackFile::STATUS_PROCESSING;
|
||||||
$this->trackFile->update();
|
$this->trackFile->save();
|
||||||
|
|
||||||
// Use the track's master file as the source
|
// Use the track's master file as the source
|
||||||
if ($this->isForUpload) {
|
if ($this->isForUpload) {
|
||||||
|
@ -120,19 +130,18 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
||||||
$process->mustRun();
|
$process->mustRun();
|
||||||
} catch (ProcessFailedException $e) {
|
} catch (ProcessFailedException $e) {
|
||||||
Log::error('An exception occured in the encoding process for track file ' . $this->trackFile->id . ' - ' . $e->getMessage());
|
Log::error('An exception occured in the encoding process for track file ' . $this->trackFile->id . ' - ' . $e->getMessage());
|
||||||
|
Log::info($process->getOutput());
|
||||||
// Ensure queue fails
|
// Ensure queue fails
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
|
||||||
Log::info($process->getOutput());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the tags of the track
|
// Update the tags of the track
|
||||||
$this->trackFile->track->updateTags($this->trackFile->format);
|
$this->trackFile->track->updateTags($this->trackFile->format);
|
||||||
|
|
||||||
// Insert the expiration time for cached tracks
|
// Insert the expiration time for cached tracks
|
||||||
if ($this->isExpirable) {
|
if ($this->isExpirable && $this->trackFile->is_cacheable) {
|
||||||
$this->trackFile->expires_at = Carbon::now()->addMinutes(Config::get('ponyfm.track_file_cache_duration'));
|
$this->trackFile->expires_at = Carbon::now()->addMinutes(Config::get('ponyfm.track_file_cache_duration'));
|
||||||
$this->trackFile->update();
|
$this->trackFile->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update file size
|
// Update file size
|
||||||
|
@ -140,7 +149,7 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
||||||
|
|
||||||
// Complete the job
|
// Complete the job
|
||||||
$this->trackFile->status = TrackFile::STATUS_NOT_BEING_PROCESSED;
|
$this->trackFile->status = TrackFile::STATUS_NOT_BEING_PROCESSED;
|
||||||
$this->trackFile->update();
|
$this->trackFile->save();
|
||||||
|
|
||||||
if ($this->isForUpload) {
|
if ($this->isForUpload) {
|
||||||
if (!$this->trackFile->is_master && $this->trackFile->is_cacheable) {
|
if (!$this->trackFile->is_master && $this->trackFile->is_cacheable) {
|
||||||
|
@ -171,6 +180,6 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
||||||
{
|
{
|
||||||
$this->trackFile->status = TrackFile::STATUS_PROCESSING_ERROR;
|
$this->trackFile->status = TrackFile::STATUS_PROCESSING_ERROR;
|
||||||
$this->trackFile->expires_at = null;
|
$this->trackFile->expires_at = null;
|
||||||
$this->trackFile->update();
|
$this->trackFile->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Jobs;
|
namespace Poniverse\Ponyfm\Jobs;
|
||||||
|
|
||||||
|
use App;
|
||||||
|
use DB;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
|
|
||||||
abstract class Job
|
abstract class Job
|
||||||
|
@ -36,4 +38,15 @@ abstract class Job
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should be called at the beginning of every job's handle()
|
||||||
|
* method. It ensures that we don't lose the in-memory database during
|
||||||
|
* testing by disconnecting from it - which causes tests to fail.
|
||||||
|
*/
|
||||||
|
protected function beforeHandle() {
|
||||||
|
if (App::environment() !== 'testing') {
|
||||||
|
DB::reconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
58
app/Jobs/UpdateSearchIndexForEntity.php
Normal file
58
app/Jobs/UpdateSearchIndexForEntity.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2016 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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\Jobs;
|
||||||
|
|
||||||
|
use DB;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Poniverse\Ponyfm\Contracts\Searchable;
|
||||||
|
use Poniverse\Ponyfm\Jobs\Job;
|
||||||
|
use Illuminate\Contracts\Bus\SelfHandling;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
class UpdateSearchIndexForEntity extends Job implements SelfHandling, ShouldQueue
|
||||||
|
{
|
||||||
|
use InteractsWithQueue, SerializesModels;
|
||||||
|
|
||||||
|
protected $entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @param Model $entity
|
||||||
|
*/
|
||||||
|
public function __construct(Searchable $entity)
|
||||||
|
{
|
||||||
|
$this->entity = $entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->beforeHandle();
|
||||||
|
$this->entity->updateElasticsearchEntrySynchronously();
|
||||||
|
}
|
||||||
|
}
|
100
app/Jobs/UpdateTagsForRenamedGenre.php
Normal file
100
app/Jobs/UpdateTagsForRenamedGenre.php
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2016 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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\Jobs;
|
||||||
|
|
||||||
|
use Auth;
|
||||||
|
use Cache;
|
||||||
|
use DB;
|
||||||
|
use Log;
|
||||||
|
use Poniverse\Ponyfm\Models\Genre;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Contracts\Bus\SelfHandling;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RenameGenre
|
||||||
|
*
|
||||||
|
* NOTE: It is assumed that the genre passed into this job has already been renamed!
|
||||||
|
* All this job does is update the tags in that genre's tracks.
|
||||||
|
*
|
||||||
|
* @package Poniverse\Ponyfm\Jobs
|
||||||
|
*/
|
||||||
|
class UpdateTagsForRenamedGenre extends Job implements SelfHandling, ShouldQueue
|
||||||
|
{
|
||||||
|
use InteractsWithQueue, SerializesModels;
|
||||||
|
|
||||||
|
protected $executingUser;
|
||||||
|
protected $genreThatWasRenamed;
|
||||||
|
protected $lockKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @param Genre $genreThatWasRenamed
|
||||||
|
*/
|
||||||
|
public function __construct(Genre $genreThatWasRenamed)
|
||||||
|
{
|
||||||
|
$this->executingUser = Auth::user();
|
||||||
|
$this->genreThatWasRenamed = $genreThatWasRenamed;
|
||||||
|
|
||||||
|
$this->lockKey = "genre-{$this->genreThatWasRenamed->id}-tag-update-lock";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->beforeHandle();
|
||||||
|
|
||||||
|
// The user who kicked off this job is used when generating revision log entries.
|
||||||
|
Auth::login($this->executingUser);
|
||||||
|
|
||||||
|
// "Lock" this genre to prevent race conditions
|
||||||
|
if (Cache::has($this->lockKey)) {
|
||||||
|
Log::info("Tag updates for the \"{$this->genreThatWasRenamed->name}\" genre are currently in progress! Will try again in 30 seconds.");
|
||||||
|
$this->release(30);
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Cache::forever($this->lockKey, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$this->genreThatWasRenamed->tracks()->chunk(200, function ($tracks) {
|
||||||
|
foreach ($tracks as $track) {
|
||||||
|
/** @var Track $track */
|
||||||
|
$track->updateTags();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Cache::forget($this->lockKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function failed()
|
||||||
|
{
|
||||||
|
Cache::forget($this->lockKey);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,27 +20,38 @@
|
||||||
|
|
||||||
class Assets
|
class Assets
|
||||||
{
|
{
|
||||||
public static function scriptIncludes($area = 'app')
|
public static function scriptIncludes(string $area) {
|
||||||
{
|
$scriptTags = '';
|
||||||
if (!Config::get("app.debug")) {
|
|
||||||
return '<script src="/build/scripts/' . $area . '.js?' . filemtime("./build/scripts/" . $area . ".js") . '"></script>';
|
if ('app' === $area) {
|
||||||
|
$scripts = ['app.js', 'templates.js'];
|
||||||
|
} elseif ('embed' === $area) {
|
||||||
|
$scripts = ['embed.js'];
|
||||||
|
} else {
|
||||||
|
throw new InvalidArgumentException('A valid app area must be specified!');
|
||||||
}
|
}
|
||||||
|
|
||||||
$scripts = self::mergeGlobs(self::getScriptsForArea($area));
|
foreach ($scripts as $filename) {
|
||||||
$retVal = "";
|
if (Config::get('app.debug') && $filename !== 'templates.js') {
|
||||||
|
$scriptTags .= "<script src='http://localhost:61999/build/scripts/{$filename}'></script>";
|
||||||
foreach ($scripts as $script) {
|
} else {
|
||||||
$filename = self::replaceExtensionWith($script, ".coffee", ".js");
|
$scriptTags .= "<script src='/build/scripts/{$filename}?" . filemtime(public_path("build/scripts/{$filename}")) . "'></script>";
|
||||||
$retVal .= "<script src='/build/$filename?" . filemtime('./build/' . $filename) . "'></script>";
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $retVal;
|
if (Config::get('app.debug')) {
|
||||||
|
$scriptTags .= '<script src="http://localhost:61999/webpack-dev-server.js"></script>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $scriptTags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function styleIncludes($area = 'app')
|
public static function styleIncludes($area = 'app')
|
||||||
{
|
{
|
||||||
if (!Config::get("app.debug")) {
|
if (!Config::get("app.debug")) {
|
||||||
return '<script>document.write(\'<link rel="stylesheet" href="build/styles/' . $area . '.css?' . filemtime("build/styles/" . $area . ".css") . '" />\');</script>';
|
return '<script>document.write(\'<link rel="stylesheet" href="build/styles/' . $area . '.css?' .
|
||||||
|
filemtime(public_path("/build/styles/${area}.css"))
|
||||||
|
. '" />\');</script>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$styles = self::mergeGlobs(self::getStylesForArea($area));
|
$styles = self::mergeGlobs(self::getStylesForArea($area));
|
||||||
|
@ -48,7 +59,7 @@ class Assets
|
||||||
|
|
||||||
foreach ($styles as $style) {
|
foreach ($styles as $style) {
|
||||||
$filename = self::replaceExtensionWith($style, ".less", ".css");
|
$filename = self::replaceExtensionWith($style, ".less", ".css");
|
||||||
$retVal .= "<link rel='stylesheet' href='/build/$filename?" . filemtime('./build/' . $filename) . "' />";
|
$retVal .= "<link rel='stylesheet' href='/build/$filename?" .filemtime(public_path("/build/${filename}")). "' />";
|
||||||
}
|
}
|
||||||
|
|
||||||
return $retVal;
|
return $retVal;
|
||||||
|
@ -82,41 +93,6 @@ class Assets
|
||||||
return $files;
|
return $files;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function getScriptsForArea($area)
|
|
||||||
{
|
|
||||||
if ($area == 'app') {
|
|
||||||
return [
|
|
||||||
"scripts/base/jquery-2.0.2.js",
|
|
||||||
"scripts/base/angular.js",
|
|
||||||
"scripts/base/marked.js",
|
|
||||||
"scripts/base/*.{coffee,js}",
|
|
||||||
"scripts/shared/*.{coffee,js}",
|
|
||||||
"scripts/app/*.{coffee,js}",
|
|
||||||
"scripts/app/services/*.{coffee,js}",
|
|
||||||
"scripts/app/filters/*.{coffee,js}",
|
|
||||||
"scripts/app/directives/*.{coffee,js}",
|
|
||||||
"scripts/app/controllers/*.{coffee,js}",
|
|
||||||
"scripts/**/*.{coffee,js}"
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
if ($area == 'embed') {
|
|
||||||
return [
|
|
||||||
"scripts/base/jquery-2.0.2.js",
|
|
||||||
"scripts/base/jquery.cookie.js",
|
|
||||||
"scripts/base/jquery.viewport.js",
|
|
||||||
"scripts/base/underscore.js",
|
|
||||||
"scripts/base/moment.js",
|
|
||||||
"scripts/base/jquery.timeago.js",
|
|
||||||
"scripts/base/soundmanager2-nodebug.js",
|
|
||||||
"scripts/shared/jquery-extensions.js",
|
|
||||||
"scripts/embed/*.coffee"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function getStylesForArea($area)
|
private static function getStylesForArea($area)
|
||||||
{
|
{
|
||||||
if ($area == 'app') {
|
if ($area == 'app') {
|
||||||
|
@ -124,7 +100,6 @@ class Assets
|
||||||
"styles/base/jquery-ui.css",
|
"styles/base/jquery-ui.css",
|
||||||
"styles/base/colorbox.css",
|
"styles/base/colorbox.css",
|
||||||
"styles/app.less",
|
"styles/app.less",
|
||||||
"styles/profiler.less"
|
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
if ($area == 'embed') {
|
if ($area == 'embed') {
|
||||||
|
|
|
@ -22,11 +22,7 @@ class AudioCache
|
||||||
{
|
{
|
||||||
private static $_movieCache = array();
|
private static $_movieCache = array();
|
||||||
|
|
||||||
/**
|
public static function get(string $filename):FFmpegMovie
|
||||||
* @param $filename
|
|
||||||
* @return FFmpegMovie
|
|
||||||
*/
|
|
||||||
public static function get($filename)
|
|
||||||
{
|
{
|
||||||
if (isset(self::$_movieCache[$filename])) {
|
if (isset(self::$_movieCache[$filename])) {
|
||||||
return self::$_movieCache[$filename];
|
return self::$_movieCache[$filename];
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pony.fm - A community for pony fan music.
|
|
||||||
* Copyright (C) 2015 Peter Deltchev
|
|
||||||
*
|
|
||||||
* 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 Assetic\Asset\BaseAsset;
|
|
||||||
use Assetic\Filter\FilterInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class CacheBusterAsset
|
|
||||||
* OH GOD IT BUUUUUUURNS
|
|
||||||
*
|
|
||||||
* Well, I may as well tell you why this awful class exists. So... Assetic doesn't quite support less's import
|
|
||||||
* directive. I mean; it supports it insofar as Less itself supports it - but it doesn't take into account the
|
|
||||||
* last modified time for imported assets. Since we only have one less file that imports everything else... well
|
|
||||||
* you can see where this is going. This asset will let us override the last modified time for an entire collection
|
|
||||||
* which allows me to write a custom mechanism for cache busting.
|
|
||||||
*/
|
|
||||||
class CacheBusterAsset extends BaseAsset
|
|
||||||
{
|
|
||||||
private $_lastModified;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param int $lastModified
|
|
||||||
*/
|
|
||||||
public function __construct($lastModified)
|
|
||||||
{
|
|
||||||
$this->_lastModified = $lastModified;
|
|
||||||
parent::__construct([], '', '', []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function load(FilterInterface $additionalFilter = null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLastModified()
|
|
||||||
{
|
|
||||||
return $this->_lastModified;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,17 +18,17 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Log;
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
class External
|
class External
|
||||||
{
|
{
|
||||||
public static function execute($command)
|
public static function execute($command)
|
||||||
{
|
{
|
||||||
$output = [];
|
$process = new Process($command);
|
||||||
$error = exec($command, $output);
|
$process->run();
|
||||||
|
|
||||||
if ($error != null) {
|
if (!$process->isSuccessful()) {
|
||||||
Log::error('"' . $command . '" failed with "' . $error . '"');
|
Log::error('"' . $command . '" failed with "' . $process->getErrorOutput() . '"');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use Illuminate\Support\Facades\URL;
|
|
||||||
|
|
||||||
class Gravatar
|
class Gravatar
|
||||||
{
|
{
|
||||||
public static function getUrl($email, $size = 80, $default = null, $rating = 'g')
|
public static function getUrl($email, $size = 80, $default = null, $rating = 'g')
|
||||||
|
@ -40,7 +38,10 @@ class Gravatar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$url .= "&d=" . urlencode(URL::to('/images/icons/profile_' . $size . '.png'));
|
// Pony.fm's production URL is hardcoded here so Gravatar can
|
||||||
|
// serve functioning default avatars in the dev environment,
|
||||||
|
// which it won't be able to access.
|
||||||
|
$url .= "&d=" . urlencode(URL::to('https://pony.fm/images/icons/profile_' . $size . '.png'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pony.fm - A community for pony fan music.
|
|
||||||
* Copyright (C) 2015 Peter Deltchev
|
|
||||||
*
|
|
||||||
* 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\Contracts\Hashing\Hasher;
|
|
||||||
|
|
||||||
class IpsHasher implements Hasher
|
|
||||||
{
|
|
||||||
public function make($value, array $options = array())
|
|
||||||
{
|
|
||||||
return md5(md5($options['salt']) . md5(static::ips_sanitize($value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function check($value, $hashedValue, array $options = array())
|
|
||||||
{
|
|
||||||
return static::make($value, ['salt' => $options['salt']]) === $hashedValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function needsRehash($hashedValue, array $options = array())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function ips_sanitize($value)
|
|
||||||
{
|
|
||||||
$value = str_replace('&', '&', $value);
|
|
||||||
$value = str_replace('\\', '\', $value);
|
|
||||||
$value = str_replace('!', '!', $value);
|
|
||||||
$value = str_replace('$', '$', $value);
|
|
||||||
$value = str_replace('"', '"', $value);
|
|
||||||
$value = str_replace('<', '<', $value);
|
|
||||||
$value = str_replace('>', '>', $value);
|
|
||||||
$value = str_replace('\'', ''', $value);
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pony.fm - A community for pony fan music.
|
* Pony.fm - A community for pony fan music.
|
||||||
|
@ -45,6 +46,27 @@ class PfmValidator extends Illuminate\Validation\Validator
|
||||||
// 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());
|
||||||
|
$codecString = $file->getAudioCodec();
|
||||||
|
|
||||||
|
// PCM, ADPCM, and AAC come in several variations as far as FFmpeg
|
||||||
|
// is concerned. They're all acceptable for Pony.fm, so we check what
|
||||||
|
// the codec string returned by FFmpeg starts with instead of looking
|
||||||
|
// for an exact match for these.
|
||||||
|
if (in_array('adpcm', $parameters) && Str::startsWith($codecString, 'adpcm')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('pcm', $parameters) && Str::startsWith($codecString, 'pcm')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('aac', $parameters) && Str::startsWith($codecString, 'aac')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array('alac', $parameters) && Str::startsWith($codecString, 'alac')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return in_array($file->getAudioCodec(), $parameters);
|
return in_array($file->getAudioCodec(), $parameters);
|
||||||
}
|
}
|
||||||
|
|
206
app/Library/Search.php
Normal file
206
app/Library/Search.php
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2016 Peter Deltchev
|
||||||
|
*
|
||||||
|
* 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\Library;
|
||||||
|
|
||||||
|
use DB;
|
||||||
|
use Elasticsearch\Client;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Poniverse\Ponyfm\Models\Album;
|
||||||
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
|
use Poniverse\Ponyfm\Models\Track;
|
||||||
|
use Poniverse\Ponyfm\Models\User;
|
||||||
|
|
||||||
|
class Search {
|
||||||
|
protected $elasticsearch;
|
||||||
|
protected $index;
|
||||||
|
|
||||||
|
public function __construct(Client $connection, string $indexName) {
|
||||||
|
$this->elasticsearch = $connection;
|
||||||
|
$this->index = $indexName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $query
|
||||||
|
* @param int $resultsPerContentType
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function searchAllContent(string $query) {
|
||||||
|
$results = $this->elasticsearch->msearch([
|
||||||
|
'index' => $this->index,
|
||||||
|
'body' => [
|
||||||
|
//===== Tracks=====//
|
||||||
|
['type' => 'track'],
|
||||||
|
[
|
||||||
|
'query' => [
|
||||||
|
'multi_match' => [
|
||||||
|
'query' => $query,
|
||||||
|
'fields' => [
|
||||||
|
'title^3',
|
||||||
|
'artist^2',
|
||||||
|
'genre',
|
||||||
|
'track_type',
|
||||||
|
'show_songs^2',
|
||||||
|
],
|
||||||
|
'tie_breaker' => 0.3,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'size' => 13
|
||||||
|
],
|
||||||
|
|
||||||
|
//===== Albums =====//
|
||||||
|
['type' => 'album'],
|
||||||
|
[
|
||||||
|
'query' => [
|
||||||
|
'multi_match' => [
|
||||||
|
'query' => $query,
|
||||||
|
'fields' => [
|
||||||
|
'title^2',
|
||||||
|
'artist',
|
||||||
|
'tracks',
|
||||||
|
],
|
||||||
|
'tie_breaker' => 0.3,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'size' => 3
|
||||||
|
],
|
||||||
|
|
||||||
|
//===== Playlists =====//
|
||||||
|
['type' => 'playlist'],
|
||||||
|
[
|
||||||
|
'query' => [
|
||||||
|
'multi_match' => [
|
||||||
|
'query' => $query,
|
||||||
|
'fields' => [
|
||||||
|
'title^3',
|
||||||
|
'curator',
|
||||||
|
'tracks^2',
|
||||||
|
],
|
||||||
|
'tie_breaker' => 0.3,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'size' => 3
|
||||||
|
],
|
||||||
|
|
||||||
|
//===== Users =====//
|
||||||
|
['type' => 'user'],
|
||||||
|
[
|
||||||
|
'query' => [
|
||||||
|
'multi_match' => [
|
||||||
|
'query' => $query,
|
||||||
|
'fields' => [
|
||||||
|
'display_name',
|
||||||
|
'tracks',
|
||||||
|
],
|
||||||
|
'tie_breaker' => 0.3,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'size' => 3
|
||||||
|
],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tracks = $this->transformTracks($results['responses'][0]['hits']['hits']);
|
||||||
|
$albums = $this->transformAlbums($results['responses'][1]['hits']['hits']);
|
||||||
|
$playlists = $this->transformPlaylists($results['responses'][2]['hits']['hits']);
|
||||||
|
$users = $this->transformUsers($results['responses'][3]['hits']['hits']);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'tracks' => $tracks,
|
||||||
|
'albums' => $albums,
|
||||||
|
'playlists' => $playlists,
|
||||||
|
'users' => $users
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function transformTracks(array $searchHits) {
|
||||||
|
$tracks = $this->transformToEloquent(Track::class, $searchHits);
|
||||||
|
$tracks = $tracks->map(function (Track $track) {
|
||||||
|
return Track::mapPublicTrackSummary($track);
|
||||||
|
});
|
||||||
|
return $tracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function transformAlbums(array $searchHits) {
|
||||||
|
$albums = $this->transformToEloquent(Album::class, $searchHits);
|
||||||
|
$albums = $albums->map(function (Album $album) {
|
||||||
|
return Album::mapPublicAlbumSummary($album);
|
||||||
|
});
|
||||||
|
return $albums;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function transformPlaylists(array $searchHits) {
|
||||||
|
$playlists = $this->transformToEloquent(Playlist::class, $searchHits);
|
||||||
|
$playlists = $playlists->map(function (Playlist $playlist) {
|
||||||
|
return Playlist::mapPublicPlaylistSummary($playlist);
|
||||||
|
});
|
||||||
|
return $playlists;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function transformUsers(array $searchHits) {
|
||||||
|
$users = $this->transformToEloquent(User::class, $searchHits);
|
||||||
|
$users = $users->map(function (User $user) {
|
||||||
|
return User::mapPublicUserSummary($user);
|
||||||
|
});
|
||||||
|
return $users;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the given Elasticsearch results into a collection of corresponding
|
||||||
|
* Eloquent models.
|
||||||
|
*
|
||||||
|
* This method assumes that the given class uses soft deletes.
|
||||||
|
*
|
||||||
|
* @param string $modelClass The Eloquent model class to instantiate these results as
|
||||||
|
* @param array $searchHits
|
||||||
|
* @return \Illuminate\Database\Eloquent\Collection
|
||||||
|
*/
|
||||||
|
protected function transformToEloquent(string $modelClass, array $searchHits) {
|
||||||
|
if (empty($searchHits)) {
|
||||||
|
return new Collection();
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = [];
|
||||||
|
$caseStatement = 'CASE id ';
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
foreach ($searchHits as $result) {
|
||||||
|
$ids[$result['_id']] = $result['_score'];
|
||||||
|
$caseStatement .= "WHEN ${result['_id']} THEN $i ";
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
$caseStatement .= 'END';
|
||||||
|
|
||||||
|
/** @var Builder $modelInstances */
|
||||||
|
$modelInstances = $modelClass::query();
|
||||||
|
|
||||||
|
if (method_exists($modelClass, 'withTrashed')) {
|
||||||
|
$modelInstances = $modelInstances->withTrashed();
|
||||||
|
}
|
||||||
|
|
||||||
|
$modelInstances = $modelInstances
|
||||||
|
->whereIn('id', array_keys($ids))
|
||||||
|
->orderBy(DB::raw($caseStatement))
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return $modelInstances;
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,24 @@
|
||||||
Version History
|
Version History
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
1.9.11: [2015-12-24] James Heinrich
|
||||||
|
* bugfix (G:64): update constructor syntax for PHP 7
|
||||||
|
* bugfix (G:62): infinite loop in large PNG files
|
||||||
|
* bugfix (G:61): ID3v2 remove BOM from frame descriptions
|
||||||
|
* bugfix (G:60): missing "break" in module.audio-video.quicktime.php
|
||||||
|
* bugfix (G:59): .gitignore comments
|
||||||
|
* bugfix (G:58): inconsistency in relation to module.tag.id3v2.php
|
||||||
|
* bugfix (G:57): comparing instead of assign
|
||||||
|
* bugfix (G:56): unsupported MIME type "audio/x-wave"
|
||||||
|
* bugfix (G:55): readme.md variable reference
|
||||||
|
* bugfix (G:54): QuickTime false 1000fps
|
||||||
|
* bugfix (G:53): Quicktime / ID3v2 multiple genres
|
||||||
|
* bugfix (G:52): sys_get_temp_dir in GetDataImageSize
|
||||||
|
* bugfix (#1903): Quicktime meta atom not parsed
|
||||||
|
* demo.joinmp3.php enhancements
|
||||||
|
* m4b (audiobook) chapters not parsed correctly
|
||||||
|
* sqlite3 caching not working
|
||||||
|
|
||||||
1.9.10: [2015-09-14] James Heinrich
|
1.9.10: [2015-09-14] James Heinrich
|
||||||
* bugfix (G:49): Declaration of getID3_cached_sqlite3
|
* bugfix (G:49): Declaration of getID3_cached_sqlite3
|
||||||
* bugfix (#1892): extension.cache.mysql
|
* bugfix (#1892): extension.cache.mysql
|
||||||
|
|
|
@ -109,7 +109,7 @@ class getID3
|
||||||
protected $startup_error = '';
|
protected $startup_error = '';
|
||||||
protected $startup_warning = '';
|
protected $startup_warning = '';
|
||||||
|
|
||||||
const VERSION = '1.9.10-201511241457';
|
const VERSION = '1.9.11-201601190922';
|
||||||
const FREAD_BUFFER_SIZE = 32768;
|
const FREAD_BUFFER_SIZE = 32768;
|
||||||
|
|
||||||
const ATTACHMENTS_NONE = false;
|
const ATTACHMENTS_NONE = false;
|
||||||
|
|
|
@ -499,6 +499,18 @@ class getid3_quicktime extends getid3_handler
|
||||||
$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8));
|
$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'covr':
|
||||||
|
$atom_structure['data'] = substr($boxdata, 8);
|
||||||
|
// not a foolproof check, but better than nothing
|
||||||
|
if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) {
|
||||||
|
$atom_structure['image_mime'] = 'image/jpeg';
|
||||||
|
} elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) {
|
||||||
|
$atom_structure['image_mime'] = 'image/png';
|
||||||
|
} elseif (preg_match('#^GIF#', $atom_structure['data'])) {
|
||||||
|
$atom_structure['image_mime'] = 'image/gif';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'atID':
|
case 'atID':
|
||||||
case 'cnID':
|
case 'cnID':
|
||||||
case 'geID':
|
case 'geID':
|
||||||
|
@ -516,9 +528,9 @@ class getid3_quicktime extends getid3_handler
|
||||||
$atom_structure['data'] = substr($boxdata, 8);
|
$atom_structure['data'] = substr($boxdata, 8);
|
||||||
if ($atomname == 'covr') {
|
if ($atomname == 'covr') {
|
||||||
// not a foolproof check, but better than nothing
|
// not a foolproof check, but better than nothing
|
||||||
if (preg_match('#^\xFF\xD8\xFF#', $atom_structure['data'])) {
|
if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) {
|
||||||
$atom_structure['image_mime'] = 'image/jpeg';
|
$atom_structure['image_mime'] = 'image/jpeg';
|
||||||
} elseif (preg_match('#^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A#', $atom_structure['data'])) {
|
} elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) {
|
||||||
$atom_structure['image_mime'] = 'image/png';
|
$atom_structure['image_mime'] = 'image/png';
|
||||||
} elseif (preg_match('#^GIF#', $atom_structure['data'])) {
|
} elseif (preg_match('#^GIF#', $atom_structure['data'])) {
|
||||||
$atom_structure['image_mime'] = 'image/gif';
|
$atom_structure['image_mime'] = 'image/gif';
|
||||||
|
@ -1579,6 +1591,10 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
|
||||||
// Furthermore, for historical reasons the list of atoms is optionally
|
// Furthermore, for historical reasons the list of atoms is optionally
|
||||||
// terminated by a 32-bit integer set to 0. If you are writing a program
|
// terminated by a 32-bit integer set to 0. If you are writing a program
|
||||||
// to read user data atoms, you should allow for the terminating 0.
|
// to read user data atoms, you should allow for the terminating 0.
|
||||||
|
if (strlen($atom_data) > 12) {
|
||||||
|
$subatomoffset += 4;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
return $atom_structure;
|
return $atom_structure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1170,9 +1170,16 @@ class getid3_riff extends getid3_handler {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'WEBP':
|
||||||
|
// https://developers.google.com/speed/webp/docs/riff_container
|
||||||
|
$info['fileformat'] = 'webp';
|
||||||
|
$info['mime_type'] = 'image/webp';
|
||||||
|
|
||||||
|
$info['error'][] = 'WebP image parsing not supported in this version of getID3()';
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
$info['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead';
|
$info['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA|WEBP), found "'.$RIFFsubtype.'" instead';
|
||||||
//unset($info['fileformat']);
|
//unset($info['fileformat']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,23 +18,53 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Helpers;
|
use Helpers;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
|
||||||
use Auth;
|
use Auth;
|
||||||
use Cache;
|
use Cache;
|
||||||
|
use Poniverse\Ponyfm\Contracts\Searchable;
|
||||||
use Poniverse\Ponyfm\Exceptions\TrackFileNotFoundException;
|
use Poniverse\Ponyfm\Exceptions\TrackFileNotFoundException;
|
||||||
|
use Poniverse\Ponyfm\Traits\IndexedInElasticsearchTrait;
|
||||||
use Poniverse\Ponyfm\Traits\TrackCollection;
|
use Poniverse\Ponyfm\Traits\TrackCollection;
|
||||||
use Poniverse\Ponyfm\Traits\SlugTrait;
|
use Poniverse\Ponyfm\Traits\SlugTrait;
|
||||||
use Venturecraft\Revisionable\RevisionableTrait;
|
use Venturecraft\Revisionable\RevisionableTrait;
|
||||||
|
|
||||||
class Album extends Model
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\Album
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property integer $user_id
|
||||||
|
* @property string $title
|
||||||
|
* @property string $slug
|
||||||
|
* @property string $description
|
||||||
|
* @property integer $cover_id
|
||||||
|
* @property integer $track_count
|
||||||
|
* @property integer $view_count
|
||||||
|
* @property integer $download_count
|
||||||
|
* @property integer $favourite_count
|
||||||
|
* @property integer $comment_count
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property string $updated_at
|
||||||
|
* @property \Carbon\Carbon $deleted_at
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\ResourceUser[] $users
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Favourite[] $favourites
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Image $cover
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Track[] $tracks
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Comment[] $comments
|
||||||
|
* @property-read mixed $url
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Venturecraft\Revisionable\Revision[] $revisionHistory
|
||||||
|
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Album userDetails()
|
||||||
|
*/
|
||||||
|
class Album extends Model implements Searchable
|
||||||
{
|
{
|
||||||
use SoftDeletes, SlugTrait, DispatchesJobs, TrackCollection, RevisionableTrait;
|
use SoftDeletes, SlugTrait, TrackCollection, RevisionableTrait, IndexedInElasticsearchTrait;
|
||||||
|
|
||||||
|
protected $elasticsearchType = 'album';
|
||||||
|
|
||||||
protected $dates = ['deleted_at'];
|
protected $dates = ['deleted_at'];
|
||||||
protected $fillable = ['user_id', 'title', 'slug'];
|
protected $fillable = ['user_id', 'title', 'slug'];
|
||||||
|
@ -62,27 +92,27 @@ class Album extends Model
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\User');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\User');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function users()
|
public function users()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\ResourceUser');
|
return $this->hasMany('Poniverse\Ponyfm\Models\ResourceUser');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function favourites()
|
public function favourites()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\Favourite');
|
return $this->hasMany('Poniverse\Ponyfm\Models\Favourite');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cover()
|
public function cover()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Image');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\Image');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tracks()
|
public function tracks()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\Track')->orderBy('track_number', 'asc');
|
return $this->hasMany('Poniverse\Ponyfm\Models\Track')->orderBy('track_number', 'asc');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function trackFiles() {
|
public function trackFiles() {
|
||||||
|
@ -91,7 +121,7 @@ class Album extends Model
|
||||||
|
|
||||||
public function comments()
|
public function comments()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\Comment')->orderBy('created_at', 'desc');
|
return $this->hasMany('Poniverse\Ponyfm\Models\Comment')->orderBy('created_at', 'desc');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mapPublicAlbumShow(Album $album)
|
public static function mapPublicAlbumShow(Album $album)
|
||||||
|
@ -182,6 +212,7 @@ class Album extends Model
|
||||||
'user' => [
|
'user' => [
|
||||||
'id' => (int) $album->user->id,
|
'id' => (int) $album->user->id,
|
||||||
'name' => $album->user->display_name,
|
'name' => $album->user->display_name,
|
||||||
|
'slug' => $album->user->slug,
|
||||||
'url' => $album->user->url,
|
'url' => $album->user->url,
|
||||||
],
|
],
|
||||||
'user_data' => $userData,
|
'user_data' => $userData,
|
||||||
|
@ -361,4 +392,40 @@ class Album extends Model
|
||||||
{
|
{
|
||||||
return 'album-'.$this->id.'-'.$key;
|
return 'album-'.$this->id.'-'.$key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of tracks in an album will always be in sync.
|
||||||
|
*
|
||||||
|
* @param array $options
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function save(array $options = []) {
|
||||||
|
$this->recountTracks();
|
||||||
|
return parent::save($options);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function recountTracks() {
|
||||||
|
$this->track_count = $this->tracks->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this model in Elasticsearch-friendly form. The array returned by
|
||||||
|
* this method should match the current mapping for this model's ES type.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toElasticsearch():array {
|
||||||
|
return [
|
||||||
|
'title' => $this->title,
|
||||||
|
'artist' => $this->user->display_name,
|
||||||
|
'tracks' => $this->tracks->pluck('title'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function shouldBeIndexed():bool {
|
||||||
|
return $this->track_count > 0 && !$this->trashed();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -18,11 +18,32 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\Comment
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property integer $user_id
|
||||||
|
* @property string $ip_address
|
||||||
|
* @property string $content
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property \Carbon\Carbon $updated_at
|
||||||
|
* @property \Carbon\Carbon $deleted_at
|
||||||
|
* @property integer $profile_id
|
||||||
|
* @property integer $track_id
|
||||||
|
* @property integer $album_id
|
||||||
|
* @property integer $playlist_id
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Track $track
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Album $album
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Playlist $playlist
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\User $profile
|
||||||
|
* @property-read mixed $resource
|
||||||
|
*/
|
||||||
class Comment extends Model
|
class Comment extends Model
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -34,27 +55,27 @@ class Comment extends Model
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\User');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\User');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function track()
|
public function track()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Track');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\Track');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function album()
|
public function album()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Album');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\Album');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function playlist()
|
public function playlist()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Playlist');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\Playlist');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function profile()
|
public function profile()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\User', 'profile_id');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\User', 'profile_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mapPublic($comment)
|
public static function mapPublic($comment)
|
|
@ -18,10 +18,26 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\Favourite
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property integer $user_id
|
||||||
|
* @property integer $track_id
|
||||||
|
* @property integer $album_id
|
||||||
|
* @property integer $playlist_id
|
||||||
|
* @property string $created_at
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Track $track
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Album $album
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Playlist $playlist
|
||||||
|
* @property-read mixed $resource
|
||||||
|
* @property-read mixed $type
|
||||||
|
*/
|
||||||
class Favourite extends Model
|
class Favourite extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'favourites';
|
protected $table = 'favourites';
|
||||||
|
@ -35,22 +51,22 @@ class Favourite extends Model
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\User');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\User');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function track()
|
public function track()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Track');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\Track');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function album()
|
public function album()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Album');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\Album');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function playlist()
|
public function playlist()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Playlist');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\Playlist');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -18,10 +18,19 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\Follower
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property integer $user_id
|
||||||
|
* @property integer $artist_id
|
||||||
|
* @property integer $playlist_id
|
||||||
|
* @property string $created_at
|
||||||
|
*/
|
||||||
class Follower extends Model
|
class Follower extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'followers';
|
protected $table = 'followers';
|
|
@ -18,7 +18,7 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use DB;
|
use DB;
|
||||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
@ -27,6 +27,22 @@ use Poniverse\Ponyfm\Traits\SlugTrait;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Venturecraft\Revisionable\RevisionableTrait;
|
use Venturecraft\Revisionable\RevisionableTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\Genre
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property string $name
|
||||||
|
* @property string $slug
|
||||||
|
* @property string $deleted_at
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Track[] $tracks
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Track $trackCountRelation
|
||||||
|
* @property-read mixed $track_count
|
||||||
|
* @property-read mixed $url
|
||||||
|
* @property-write mixed $title
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Venturecraft\Revisionable\Revision[] $revisionHistory
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property \Carbon\Carbon $updated_at
|
||||||
|
*/
|
||||||
class Genre extends Model
|
class Genre extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'genres';
|
protected $table = 'genres';
|
||||||
|
@ -35,8 +51,6 @@ class Genre extends Model
|
||||||
protected $appends = ['track_count', 'url'];
|
protected $appends = ['track_count', 'url'];
|
||||||
protected $hidden = ['trackCountRelation'];
|
protected $hidden = ['trackCountRelation'];
|
||||||
|
|
||||||
public $timestamps = false;
|
|
||||||
|
|
||||||
use SlugTrait, SoftDeletes, RevisionableTrait;
|
use SlugTrait, SoftDeletes, RevisionableTrait;
|
||||||
|
|
||||||
public function tracks(){
|
public function tracks(){
|
|
@ -18,13 +18,27 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use External;
|
use External;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Config;
|
use Config;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\Image
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property string $filename
|
||||||
|
* @property string $mime
|
||||||
|
* @property string $extension
|
||||||
|
* @property integer $size
|
||||||
|
* @property string $hash
|
||||||
|
* @property integer $uploaded_by
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property \Carbon\Carbon $updated_at
|
||||||
|
*/
|
||||||
class Image extends Model
|
class Image extends Model
|
||||||
{
|
{
|
||||||
const NORMAL = 1;
|
const NORMAL = 1;
|
||||||
|
@ -52,7 +66,14 @@ class Image extends Model
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function upload(UploadedFile $file, $user)
|
/**
|
||||||
|
* @param UploadedFile $file
|
||||||
|
* @param int|User $user
|
||||||
|
* @param bool $forceReupload forces the image to be re-processed even if a matching hash is found
|
||||||
|
* @return Image
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public static function upload(UploadedFile $file, $user, bool $forceReupload = false)
|
||||||
{
|
{
|
||||||
$userId = $user;
|
$userId = $user;
|
||||||
if ($user instanceof User) {
|
if ($user instanceof User) {
|
||||||
|
@ -63,10 +84,25 @@ class Image extends Model
|
||||||
$image = Image::whereHash($hash)->whereUploadedBy($userId)->first();
|
$image = Image::whereHash($hash)->whereUploadedBy($userId)->first();
|
||||||
|
|
||||||
if ($image) {
|
if ($image) {
|
||||||
|
if ($forceReupload) {
|
||||||
|
// delete existing versions of the image
|
||||||
|
$filenames = scandir($image->getDirectory());
|
||||||
|
$imagePrefix = $image->id.'_';
|
||||||
|
|
||||||
|
$filenames = array_filter($filenames, function(string $filename) use ($imagePrefix) {
|
||||||
|
return Str::startsWith($filename, $imagePrefix);
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach($filenames as $filename) {
|
||||||
|
unlink($image->getDirectory().'/'.$filename);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return $image;
|
return $image;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
$image = new Image();
|
$image = new Image();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$image->uploaded_by = $userId;
|
$image->uploaded_by = $userId;
|
||||||
$image->size = $file->getSize();
|
$image->size = $file->getSize();
|
||||||
|
@ -79,7 +115,7 @@ class Image extends Model
|
||||||
$image->ensureDirectoryExists();
|
$image->ensureDirectoryExists();
|
||||||
foreach (self::$ImageTypes as $coverType) {
|
foreach (self::$ImageTypes as $coverType) {
|
||||||
if ($coverType['id'] === self::ORIGINAL && $image->mime === 'image/jpeg') {
|
if ($coverType['id'] === self::ORIGINAL && $image->mime === 'image/jpeg') {
|
||||||
$command = 'cp '.$file->getPathname().' '.$image->getFile($coverType['id']);
|
$command = 'cp "'.$file->getPathname().'" '.$image->getFile($coverType['id']);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// ImageMagick options reference: http://www.imagemagick.org/script/command-line-options.php
|
// ImageMagick options reference: http://www.imagemagick.org/script/command-line-options.php
|
||||||
|
@ -100,6 +136,7 @@ class Image extends Model
|
||||||
}
|
}
|
||||||
|
|
||||||
External::execute($command);
|
External::execute($command);
|
||||||
|
chmod($image->getFile($coverType['id']), 0644);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $image;
|
return $image;
|
|
@ -18,10 +18,20 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\License
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property string $title
|
||||||
|
* @property string $description
|
||||||
|
* @property boolean $affiliate_distribution
|
||||||
|
* @property boolean $open_distribution
|
||||||
|
* @property boolean $remix
|
||||||
|
*/
|
||||||
class License extends Model
|
class License extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'licenses';
|
protected $table = 'licenses';
|
|
@ -18,21 +18,32 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\PinnedPlaylist
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property integer $user_id
|
||||||
|
* @property integer $playlist_id
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property \Carbon\Carbon $updated_at
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Playlist $playlist
|
||||||
|
*/
|
||||||
class PinnedPlaylist extends Model
|
class PinnedPlaylist extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'pinned_playlists';
|
protected $table = 'pinned_playlists';
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\User');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\User');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function playlist()
|
public function playlist()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Playlist');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\Playlist');
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,7 +18,7 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Helpers;
|
use Helpers;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
@ -26,18 +26,61 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Cache;
|
use Cache;
|
||||||
|
use Poniverse\Ponyfm\Contracts\Searchable;
|
||||||
use Poniverse\Ponyfm\Exceptions\TrackFileNotFoundException;
|
use Poniverse\Ponyfm\Exceptions\TrackFileNotFoundException;
|
||||||
|
use Poniverse\Ponyfm\Traits\IndexedInElasticsearchTrait;
|
||||||
use Poniverse\Ponyfm\Traits\TrackCollection;
|
use Poniverse\Ponyfm\Traits\TrackCollection;
|
||||||
use Poniverse\Ponyfm\Traits\SlugTrait;
|
use Poniverse\Ponyfm\Traits\SlugTrait;
|
||||||
use Venturecraft\Revisionable\RevisionableTrait;
|
use Venturecraft\Revisionable\RevisionableTrait;
|
||||||
|
|
||||||
class Playlist extends Model
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\Playlist
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property integer $user_id
|
||||||
|
* @property string $title
|
||||||
|
* @property string $slug
|
||||||
|
* @property string $description
|
||||||
|
* @property boolean $is_public
|
||||||
|
* @property integer $track_count
|
||||||
|
* @property integer $view_count
|
||||||
|
* @property integer $download_count
|
||||||
|
* @property integer $favourite_count
|
||||||
|
* @property integer $follow_count
|
||||||
|
* @property integer $comment_count
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property \Carbon\Carbon $updated_at
|
||||||
|
* @property \Carbon\Carbon $deleted_at
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Track[] $tracks
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\ResourceUser[] $users
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Comment[] $comments
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\PinnedPlaylist[] $pins
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||||
|
* @property-read mixed $url
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Venturecraft\Revisionable\Revision[] $revisionHistory
|
||||||
|
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Playlist userDetails()
|
||||||
|
*/
|
||||||
|
class Playlist extends Model implements Searchable
|
||||||
{
|
{
|
||||||
use SoftDeletes, SlugTrait, DispatchesJobs, TrackCollection, RevisionableTrait;
|
use SoftDeletes, SlugTrait, TrackCollection, RevisionableTrait, IndexedInElasticsearchTrait;
|
||||||
|
|
||||||
|
protected $elasticsearchType = 'playlist';
|
||||||
|
|
||||||
protected $table = 'playlists';
|
protected $table = 'playlists';
|
||||||
|
|
||||||
protected $dates = ['deleted_at'];
|
protected $dates = ['deleted_at'];
|
||||||
|
protected $casts = [
|
||||||
|
'id' => 'integer',
|
||||||
|
'user_id' => 'integer',
|
||||||
|
'title' => 'string',
|
||||||
|
'description' => 'string',
|
||||||
|
'is_public' => 'boolean',
|
||||||
|
'track_count' => 'integer',
|
||||||
|
'view_count' => 'integer',
|
||||||
|
'download_count' => 'integer',
|
||||||
|
'favourte_count' => 'integer',
|
||||||
|
'follow_count' => 'integer',
|
||||||
|
'comment_count' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
public static function summary()
|
public static function summary()
|
||||||
{
|
{
|
||||||
|
@ -152,7 +195,7 @@ class Playlist extends Model
|
||||||
public function tracks()
|
public function tracks()
|
||||||
{
|
{
|
||||||
return $this
|
return $this
|
||||||
->belongsToMany('Poniverse\Ponyfm\Track')
|
->belongsToMany('Poniverse\Ponyfm\Models\Track')
|
||||||
->withPivot('position')
|
->withPivot('position')
|
||||||
->withTimestamps()
|
->withTimestamps()
|
||||||
->orderBy('position', 'asc');
|
->orderBy('position', 'asc');
|
||||||
|
@ -166,22 +209,22 @@ class Playlist extends Model
|
||||||
|
|
||||||
public function users()
|
public function users()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\ResourceUser');
|
return $this->hasMany('Poniverse\Ponyfm\Models\ResourceUser');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function comments()
|
public function comments()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\Comment')->orderBy('created_at', 'desc');
|
return $this->hasMany('Poniverse\Ponyfm\Models\Comment')->orderBy('created_at', 'desc');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function pins()
|
public function pins()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\PinnedPlaylist');
|
return $this->hasMany('Poniverse\Ponyfm\Models\PinnedPlaylist');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\User');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\User');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function hasPinFor($userId)
|
public function hasPinFor($userId)
|
||||||
|
@ -258,4 +301,27 @@ class Playlist extends Model
|
||||||
{
|
{
|
||||||
return 'playlist-' . $this->id . '-' . $key;
|
return 'playlist-' . $this->id . '-' . $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this model in Elasticsearch-friendly form. The array returned by
|
||||||
|
* this method should match the current mapping for this model's ES type.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toElasticsearch():array {
|
||||||
|
return [
|
||||||
|
'title' => $this->title,
|
||||||
|
'curator' => $this->user->display_name,
|
||||||
|
'tracks' => $this->tracks->pluck('title'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function shouldBeIndexed():bool {
|
||||||
|
return $this->is_public &&
|
||||||
|
$this->track_count > 0 &&
|
||||||
|
!$this->trashed();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -18,7 +18,7 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
@ -26,6 +26,19 @@ use Auth;
|
||||||
use DB;
|
use DB;
|
||||||
use Request;
|
use Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\ResourceLogItem
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property integer $user_id
|
||||||
|
* @property integer $log_type
|
||||||
|
* @property string $ip_address
|
||||||
|
* @property integer $track_format_id
|
||||||
|
* @property integer $track_id
|
||||||
|
* @property integer $album_id
|
||||||
|
* @property integer $playlist_id
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
*/
|
||||||
class ResourceLogItem extends Model
|
class ResourceLogItem extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'resource_log_items';
|
protected $table = 'resource_log_items';
|
|
@ -18,10 +18,26 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\ResourceUser
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property integer $user_id
|
||||||
|
* @property integer $track_id
|
||||||
|
* @property integer $album_id
|
||||||
|
* @property integer $playlist_id
|
||||||
|
* @property integer $artist_id
|
||||||
|
* @property boolean $is_followed
|
||||||
|
* @property boolean $is_favourited
|
||||||
|
* @property boolean $is_pinned
|
||||||
|
* @property integer $view_count
|
||||||
|
* @property integer $play_count
|
||||||
|
* @property integer $download_count
|
||||||
|
*/
|
||||||
class ResourceUser extends Model
|
class ResourceUser extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'resource_users';
|
protected $table = 'resource_users';
|
|
@ -18,10 +18,17 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\Role
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property string $name
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\User[] $users
|
||||||
|
*/
|
||||||
class Role extends Model
|
class Role extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'roles';
|
protected $table = 'roles';
|
|
@ -18,10 +18,18 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\ShowSong
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property string $title
|
||||||
|
* @property string $lyrics
|
||||||
|
* @property string $slug
|
||||||
|
*/
|
||||||
class ShowSong extends Model
|
class ShowSong extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'show_songs';
|
protected $table = 'show_songs';
|
|
@ -18,13 +18,16 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Auth;
|
use Auth;
|
||||||
use Cache;
|
use Cache;
|
||||||
use Config;
|
use Config;
|
||||||
use DB;
|
use DB;
|
||||||
|
use Gate;
|
||||||
|
use Poniverse\Ponyfm\Contracts\Searchable;
|
||||||
use Poniverse\Ponyfm\Exceptions\TrackFileNotFoundException;
|
use Poniverse\Ponyfm\Exceptions\TrackFileNotFoundException;
|
||||||
|
use Poniverse\Ponyfm\Traits\IndexedInElasticsearchTrait;
|
||||||
use Poniverse\Ponyfm\Traits\SlugTrait;
|
use Poniverse\Ponyfm\Traits\SlugTrait;
|
||||||
use Exception;
|
use Exception;
|
||||||
use External;
|
use External;
|
||||||
|
@ -36,9 +39,68 @@ use Illuminate\Support\Str;
|
||||||
use Log;
|
use Log;
|
||||||
use Venturecraft\Revisionable\RevisionableTrait;
|
use Venturecraft\Revisionable\RevisionableTrait;
|
||||||
|
|
||||||
class Track extends Model
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\Track
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property integer $user_id
|
||||||
|
* @property integer $license_id
|
||||||
|
* @property integer $genre_id
|
||||||
|
* @property integer $track_type_id
|
||||||
|
* @property string $title
|
||||||
|
* @property string $slug
|
||||||
|
* @property string $description
|
||||||
|
* @property string $lyrics
|
||||||
|
* @property boolean $is_vocal
|
||||||
|
* @property boolean $is_explicit
|
||||||
|
* @property integer $cover_id
|
||||||
|
* @property boolean $is_downloadable
|
||||||
|
* @property float $duration
|
||||||
|
* @property integer $play_count
|
||||||
|
* @property integer $view_count
|
||||||
|
* @property integer $download_count
|
||||||
|
* @property integer $favourite_count
|
||||||
|
* @property integer $comment_count
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property string $updated_at
|
||||||
|
* @property \Carbon\Carbon $deleted_at
|
||||||
|
* @property \Carbon\Carbon $published_at
|
||||||
|
* @property \Carbon\Carbon $released_at
|
||||||
|
* @property integer $album_id
|
||||||
|
* @property integer $track_number
|
||||||
|
* @property boolean $is_latest
|
||||||
|
* @property string $hash
|
||||||
|
* @property boolean $is_listed
|
||||||
|
* @property string $source
|
||||||
|
* @property string $original_tags
|
||||||
|
* @property string $metadata
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Genre $genre
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\TrackType $trackType
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Comment[] $comments
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Favourite[] $favourites
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Image $cover
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\ShowSong[] $showSongs
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\ResourceUser[] $users
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Album $album
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\TrackFile[] $trackFiles
|
||||||
|
* @property-read mixed $year
|
||||||
|
* @property-read mixed $url
|
||||||
|
* @property-read mixed $download_directory
|
||||||
|
* @property-read mixed $status
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Venturecraft\Revisionable\Revision[] $revisionHistory
|
||||||
|
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Track userDetails()
|
||||||
|
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Track published()
|
||||||
|
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Track listed()
|
||||||
|
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Track explicitFilter()
|
||||||
|
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Track withComments()
|
||||||
|
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Track mlpma()
|
||||||
|
*/
|
||||||
|
class Track extends Model implements Searchable
|
||||||
{
|
{
|
||||||
use SoftDeletes;
|
use SoftDeletes, IndexedInElasticsearchTrait;
|
||||||
|
|
||||||
|
protected $elasticsearchType = 'track';
|
||||||
|
|
||||||
protected $dates = ['deleted_at', 'published_at', 'released_at'];
|
protected $dates = ['deleted_at', 'published_at', 'released_at'];
|
||||||
protected $hidden = ['original_tags', 'metadata'];
|
protected $hidden = ['original_tags', 'metadata'];
|
||||||
|
@ -192,6 +254,9 @@ class Track extends Model
|
||||||
$query->join('mlpma_tracks', 'tracks.id', '=', 'mlpma_tracks.track_id');
|
$query->join('mlpma_tracks', 'tracks.id', '=', 'mlpma_tracks.track_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param integer $count
|
||||||
|
*/
|
||||||
public static function popular($count, $allowExplicit = false)
|
public static function popular($count, $allowExplicit = false)
|
||||||
{
|
{
|
||||||
$trackIds = Cache::remember('popular_tracks'.$count.'-'.($allowExplicit ? 'explicit' : 'safe'), 5,
|
$trackIds = Cache::remember('popular_tracks'.$count.'-'.($allowExplicit ? 'explicit' : 'safe'), 5,
|
||||||
|
@ -238,6 +303,10 @@ class Track extends Model
|
||||||
$processed[] = Track::mapPublicTrackSummary($track);
|
$processed[] = Track::mapPublicTrackSummary($track);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Songs that get played more should drop down
|
||||||
|
// in the list so they don't hog the top spots.
|
||||||
|
array_reverse($processed);
|
||||||
|
|
||||||
return $processed;
|
return $processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,11 +397,11 @@ class Track extends Model
|
||||||
],
|
],
|
||||||
'url' => $track->url,
|
'url' => $track->url,
|
||||||
'slug' => $track->slug,
|
'slug' => $track->slug,
|
||||||
'is_vocal' => (bool)$track->is_vocal,
|
'is_vocal' => $track->is_vocal,
|
||||||
'is_explicit' => (bool)$track->is_explicit,
|
'is_explicit' => $track->is_explicit,
|
||||||
'is_downloadable' => (bool)$track->is_downloadable,
|
'is_downloadable' => $track->is_downloadable,
|
||||||
'is_published' => (bool)$track->isPublished(),
|
'is_published' => $track->isPublished(),
|
||||||
'published_at' => $track->published_at->format('c'),
|
'published_at' => $track->isPublished() ? $track->published_at->format('c') : null,
|
||||||
'duration' => $track->duration,
|
'duration' => $track->duration,
|
||||||
'genre' => $track->genre != null
|
'genre' => $track->genre != null
|
||||||
?
|
?
|
||||||
|
@ -355,8 +424,8 @@ class Track extends Model
|
||||||
],
|
],
|
||||||
'user_data' => $userData,
|
'user_data' => $userData,
|
||||||
'permissions' => [
|
'permissions' => [
|
||||||
'delete' => Auth::check() && Auth::user()->id == $track->user_id,
|
'delete' => Gate::allows('delete', $track),
|
||||||
'edit' => Auth::check() && Auth::user()->id == $track->user_id
|
'edit' => Gate::allows('edit', $track)
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -371,9 +440,10 @@ class Track extends Model
|
||||||
$returnValue = self::mapPrivateTrackSummary($track);
|
$returnValue = self::mapPrivateTrackSummary($track);
|
||||||
$returnValue['album_id'] = $track->album_id;
|
$returnValue['album_id'] = $track->album_id;
|
||||||
$returnValue['show_songs'] = $showSongs;
|
$returnValue['show_songs'] = $showSongs;
|
||||||
|
$returnValue['cover_id'] = $track->cover_id;
|
||||||
$returnValue['real_cover_url'] = $track->getCoverUrl(Image::NORMAL);
|
$returnValue['real_cover_url'] = $track->getCoverUrl(Image::NORMAL);
|
||||||
$returnValue['cover_url'] = $track->hasCover() ? $track->getCoverUrl(Image::NORMAL) : null;
|
$returnValue['cover_url'] = $track->hasCover() ? $track->getCoverUrl(Image::NORMAL) : null;
|
||||||
$returnValue['released_at'] = $track->released_at;
|
$returnValue['released_at'] = $track->released_at ? $track->released_at->toDateString() : null;
|
||||||
$returnValue['lyrics'] = $track->lyrics;
|
$returnValue['lyrics'] = $track->lyrics;
|
||||||
$returnValue['description'] = $track->description;
|
$returnValue['description'] = $track->description;
|
||||||
$returnValue['is_downloadable'] = !$track->isPublished() ? true : (bool) $track->is_downloadable;
|
$returnValue['is_downloadable'] = !$track->isPublished() ? true : (bool) $track->is_downloadable;
|
||||||
|
@ -407,52 +477,52 @@ class Track extends Model
|
||||||
|
|
||||||
public function genre()
|
public function genre()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Genre');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\Genre');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function trackType()
|
public function trackType()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\TrackType', 'track_type_id');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\TrackType', 'track_type_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function comments()
|
public function comments()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\Comment')->orderBy('created_at', 'desc');
|
return $this->hasMany('Poniverse\Ponyfm\Models\Comment')->orderBy('created_at', 'desc');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function favourites()
|
public function favourites()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\Favourite');
|
return $this->hasMany('Poniverse\Ponyfm\Models\Favourite');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cover()
|
public function cover()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Image');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\Image');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showSongs()
|
public function showSongs()
|
||||||
{
|
{
|
||||||
return $this->belongsToMany('Poniverse\Ponyfm\ShowSong');
|
return $this->belongsToMany('Poniverse\Ponyfm\Models\ShowSong');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function users()
|
public function users()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\ResourceUser');
|
return $this->hasMany('Poniverse\Ponyfm\Models\ResourceUser');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\User');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\User');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function album()
|
public function album()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Album');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\Album');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function trackFiles()
|
public function trackFiles()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\TrackFile');
|
return $this->hasMany('Poniverse\Ponyfm\Models\TrackFile');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getYearAttribute()
|
public function getYearAttribute()
|
||||||
|
@ -592,6 +662,9 @@ class Track extends Model
|
||||||
return "{$this->title}.{$format['extension']}";
|
return "{$this->title}.{$format['extension']}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getFileFor($format)
|
public function getFileFor($format)
|
||||||
{
|
{
|
||||||
if (!isset(self::$Formats[$format])) {
|
if (!isset(self::$Formats[$format])) {
|
||||||
|
@ -610,7 +683,7 @@ class Track extends Model
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getTemporarySourceFile() {
|
public function getTemporarySourceFile():string {
|
||||||
return Config::get('ponyfm.files_directory').'/queued-tracks/'.$this->id;
|
return Config::get('ponyfm.files_directory').'/queued-tracks/'.$this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -637,11 +710,11 @@ class Track extends Model
|
||||||
|
|
||||||
} elseif (
|
} elseif (
|
||||||
$carry !== static::STATUS_ERROR &&
|
$carry !== static::STATUS_ERROR &&
|
||||||
(int) $trackFile->status === TrackFile::STATUS_PROCESSING) {
|
in_array($trackFile->status, [TrackFile::STATUS_PROCESSING, TrackFile::STATUS_PROCESSING_PENDING])) {
|
||||||
return static::STATUS_PROCESSING;
|
return static::STATUS_PROCESSING;
|
||||||
|
|
||||||
} elseif (
|
} elseif (
|
||||||
!in_array($carry, [static::STATUS_ERROR, static::STATUS_PROCESSING]) &&
|
!in_array($carry, [static::STATUS_ERROR, static::STATUS_PROCESSING, TrackFile::STATUS_PROCESSING_PENDING]) &&
|
||||||
(int) $trackFile->status === TrackFile::STATUS_NOT_BEING_PROCESSED
|
(int) $trackFile->status === TrackFile::STATUS_NOT_BEING_PROCESSED
|
||||||
) {
|
) {
|
||||||
return static::STATUS_COMPLETE;
|
return static::STATUS_COMPLETE;
|
||||||
|
@ -670,7 +743,7 @@ class Track extends Model
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateTagsForTrackFile($trackFile) {
|
private function updateTagsForTrackFile(TrackFile $trackFile) {
|
||||||
$trackFile->touch();
|
$trackFile->touch();
|
||||||
|
|
||||||
if (\File::exists($trackFile->getFile())) {
|
if (\File::exists($trackFile->getFile())) {
|
||||||
|
@ -699,7 +772,7 @@ class Track extends Model
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->cover !== null) {
|
if ($this->cover !== null) {
|
||||||
$command .= '--artwork ' . $this->cover->getFile() . ' ';
|
$command .= '--artwork '.$this->cover->getFile(Image::ORIGINAL).' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
$command .= '--overWrite';
|
$command .= '--overWrite';
|
||||||
|
@ -740,7 +813,7 @@ class Track extends Model
|
||||||
|
|
||||||
if ($format == 'MP3' && $this->cover_id != null && is_file($this->cover->getFile())) {
|
if ($format == 'MP3' && $this->cover_id != null && is_file($this->cover->getFile())) {
|
||||||
$tagWriter->tag_data['attached_picture'][0] = [
|
$tagWriter->tag_data['attached_picture'][0] = [
|
||||||
'data' => file_get_contents($this->cover->getFile()),
|
'data' => file_get_contents($this->cover->getFile(Image::ORIGINAL)),
|
||||||
'picturetypeid' => 2,
|
'picturetypeid' => 2,
|
||||||
'description' => 'cover',
|
'description' => 'cover',
|
||||||
'mime' => $this->cover->mime
|
'mime' => $this->cover->mime
|
||||||
|
@ -765,4 +838,28 @@ class Track extends Model
|
||||||
{
|
{
|
||||||
return 'track-'.$this->id.'-'.$key;
|
return 'track-'.$this->id.'-'.$key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function shouldBeIndexed():bool {
|
||||||
|
return $this->is_listed &&
|
||||||
|
$this->published_at !== null &&
|
||||||
|
!$this->trashed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function toElasticsearch():array {
|
||||||
|
return [
|
||||||
|
'title' => $this->title,
|
||||||
|
'artist' => $this->user->display_name,
|
||||||
|
'published_at' => $this->published_at ? $this->published_at->toIso8601String() : null,
|
||||||
|
'genre' => $this->genre->name,
|
||||||
|
'track_type' => $this->trackType->title,
|
||||||
|
'show_songs' => $this->showSongs->pluck('title')
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -18,7 +18,7 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Config;
|
use Config;
|
||||||
use Helpers;
|
use Helpers;
|
||||||
|
@ -26,17 +26,47 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
use App;
|
use App;
|
||||||
use File;
|
use File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\TrackFile
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property integer $track_id
|
||||||
|
* @property boolean $is_master
|
||||||
|
* @property string $format
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property \Carbon\Carbon $updated_at
|
||||||
|
* @property boolean $is_cacheable
|
||||||
|
* @property boolean $status
|
||||||
|
* @property \Carbon\Carbon $expires_at
|
||||||
|
* @property integer $filesize
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Track $track
|
||||||
|
* @property-read mixed $extension
|
||||||
|
* @property-read mixed $url
|
||||||
|
* @property-read mixed $size
|
||||||
|
* @property-read mixed $is_expired
|
||||||
|
*/
|
||||||
class TrackFile extends Model
|
class TrackFile extends Model
|
||||||
{
|
{
|
||||||
// used for the "status" property
|
// used for the "status" property
|
||||||
const STATUS_NOT_BEING_PROCESSED = 0;
|
const STATUS_NOT_BEING_PROCESSED = 0;
|
||||||
const STATUS_PROCESSING = 1;
|
const STATUS_PROCESSING = 1;
|
||||||
const STATUS_PROCESSING_ERROR = 2;
|
const STATUS_PROCESSING_ERROR = 2;
|
||||||
|
const STATUS_PROCESSING_PENDING = 3;
|
||||||
|
|
||||||
|
protected $appends = ['is_expired'];
|
||||||
|
protected $dates = ['expires_at'];
|
||||||
|
protected $casts = [
|
||||||
|
'id' => 'integer',
|
||||||
|
'track_id' => 'integer',
|
||||||
|
'is_master' => 'boolean',
|
||||||
|
'format' => 'string',
|
||||||
|
'is_cacheable' => 'boolean',
|
||||||
|
'status' => 'integer',
|
||||||
|
'filesize' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
public function track()
|
public function track() {
|
||||||
{
|
return $this->belongsTo(Track::class)->withTrashed();
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Track')->withTrashed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,6 +105,11 @@ class TrackFile extends Model
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getIsExpiredAttribute() {
|
||||||
|
return $this->attributes['expires_at'] === null ||
|
||||||
|
$this->expires_at->isPast();
|
||||||
|
}
|
||||||
|
|
||||||
public function getFormatAttribute($value)
|
public function getFormatAttribute($value)
|
||||||
{
|
{
|
||||||
return $value;
|
return $value;
|
|
@ -18,10 +18,17 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\TrackType
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property string $title
|
||||||
|
* @property string $editor_title
|
||||||
|
*/
|
||||||
class TrackType extends Model
|
class TrackType extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'track_types';
|
protected $table = 'track_types';
|
|
@ -18,7 +18,7 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm\Models;
|
||||||
|
|
||||||
use Gravatar;
|
use Gravatar;
|
||||||
use Illuminate\Auth\Authenticatable;
|
use Illuminate\Auth\Authenticatable;
|
||||||
|
@ -29,11 +29,46 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Foundation\Auth\Access\Authorizable;
|
use Illuminate\Foundation\Auth\Access\Authorizable;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Poniverse\Ponyfm\Contracts\Searchable;
|
||||||
|
use Poniverse\Ponyfm\Traits\IndexedInElasticsearchTrait;
|
||||||
use Venturecraft\Revisionable\RevisionableTrait;
|
use Venturecraft\Revisionable\RevisionableTrait;
|
||||||
|
|
||||||
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, \Illuminate\Contracts\Auth\Access\Authorizable
|
/**
|
||||||
|
* Poniverse\Ponyfm\Models\User
|
||||||
|
*
|
||||||
|
* @property integer $id
|
||||||
|
* @property string $display_name
|
||||||
|
* @property string $username
|
||||||
|
* @property boolean $sync_names
|
||||||
|
* @property string $email
|
||||||
|
* @property string $gravatar
|
||||||
|
* @property string $slug
|
||||||
|
* @property boolean $uses_gravatar
|
||||||
|
* @property boolean $can_see_explicit_content
|
||||||
|
* @property string $bio
|
||||||
|
* @property integer $track_count
|
||||||
|
* @property integer $comment_count
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property \Carbon\Carbon $updated_at
|
||||||
|
* @property integer $avatar_id
|
||||||
|
* @property string $remember_token
|
||||||
|
* @property boolean $is_archived
|
||||||
|
* @property \Carbon\Carbon $disabled_at
|
||||||
|
* @property-read \Poniverse\Ponyfm\Models\Image $avatar
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\ResourceUser[] $users
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Role[] $roles
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Comment[] $comments
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Track[] $tracks
|
||||||
|
* @property-read mixed $url
|
||||||
|
* @property-read mixed $message_url
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection|\Venturecraft\Revisionable\Revision[] $revisionHistory
|
||||||
|
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\User userDetails()
|
||||||
|
*/
|
||||||
|
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, \Illuminate\Contracts\Auth\Access\Authorizable, Searchable
|
||||||
{
|
{
|
||||||
use Authenticatable, CanResetPassword, Authorizable, RevisionableTrait;
|
use Authenticatable, CanResetPassword, Authorizable, RevisionableTrait, IndexedInElasticsearchTrait;
|
||||||
|
|
||||||
|
protected $elasticsearchType = 'user';
|
||||||
|
|
||||||
protected $table = 'users';
|
protected $table = 'users';
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
@ -46,6 +81,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||||
'avatar_id' => 'integer',
|
'avatar_id' => 'integer',
|
||||||
'is_archived' => 'boolean',
|
'is_archived' => 'boolean',
|
||||||
];
|
];
|
||||||
|
protected $dates = ['created_at', 'updated_at', 'disabled_at'];
|
||||||
|
protected $hidden = ['disabled_at'];
|
||||||
|
|
||||||
public function scopeUserDetails($query)
|
public function scopeUserDetails($query)
|
||||||
{
|
{
|
||||||
|
@ -89,12 +126,12 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||||
|
|
||||||
public function avatar()
|
public function avatar()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Image');
|
return $this->belongsTo('Poniverse\Ponyfm\Models\Image');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function users()
|
public function users()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\ResourceUser', 'artist_id');
|
return $this->hasMany('Poniverse\Ponyfm\Models\ResourceUser', 'artist_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function roles()
|
public function roles()
|
||||||
|
@ -104,12 +141,12 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||||
|
|
||||||
public function comments()
|
public function comments()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\Comment', 'profile_id')->orderBy('created_at', 'desc');
|
return $this->hasMany('Poniverse\Ponyfm\Models\Comment', 'profile_id')->orderBy('created_at', 'desc');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tracks()
|
public function tracks()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\Track', 'user_id');
|
return $this->hasMany('Poniverse\Ponyfm\Models\Track', 'user_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIsArchivedAttribute()
|
public function getIsArchivedAttribute()
|
||||||
|
@ -201,10 +238,10 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||||
/**
|
/**
|
||||||
* Returns true if this user has the given role.
|
* Returns true if this user has the given role.
|
||||||
*
|
*
|
||||||
* @param $roleName
|
* @param string $roleName
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function hasRole($roleName)
|
public function hasRole($roleName):bool
|
||||||
{
|
{
|
||||||
foreach ($this->roles as $role) {
|
foreach ($this->roles as $role) {
|
||||||
if ($role->name === $roleName) {
|
if ($role->name === $roleName) {
|
||||||
|
@ -214,4 +251,40 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function mapPublicUserSummary(User $user) {
|
||||||
|
return [
|
||||||
|
'id' => $user->id,
|
||||||
|
'name' => $user->display_name,
|
||||||
|
'slug' => $user->slug,
|
||||||
|
'url' => $user->url,
|
||||||
|
'is_archived' => $user->is_archived,
|
||||||
|
'avatars' => [
|
||||||
|
'small' => $user->getAvatarUrl(Image::SMALL),
|
||||||
|
'normal' => $user->getAvatarUrl(Image::NORMAL)
|
||||||
|
],
|
||||||
|
'created_at' => $user->created_at
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns this model in Elasticsearch-friendly form. The array returned by
|
||||||
|
* this method should match the current mapping for this model's ES type.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toElasticsearch():array {
|
||||||
|
return [
|
||||||
|
'username' => $this->username,
|
||||||
|
'display_name' => $this->display_name,
|
||||||
|
'tracks' => $this->tracks->pluck('title'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public function shouldBeIndexed():bool {
|
||||||
|
return $this->disabled_at === null;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -20,11 +20,19 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm;
|
||||||
|
|
||||||
|
use Poniverse\Ponyfm\Models\Playlist;
|
||||||
use ZipStream;
|
use ZipStream;
|
||||||
|
|
||||||
class PlaylistDownloader
|
class PlaylistDownloader
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var Playlist
|
||||||
|
*/
|
||||||
private $_playlist;
|
private $_playlist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
private $_format;
|
private $_format;
|
||||||
|
|
||||||
function __construct($playlist, $format)
|
function __construct($playlist, $format)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue