mirror of
https://github.com/koel/koel
synced 2024-11-24 13:13:05 +00:00
Support force and selective-tags sync
This commit is contained in:
parent
66f68d65cf
commit
d8d2dc8a5d
6 changed files with 380 additions and 162 deletions
|
@ -15,7 +15,9 @@ class SyncMedia extends Command
|
|||
* @var string
|
||||
*/
|
||||
protected $signature = 'koel:sync
|
||||
{record? : A single watch record. Consult Wiki for more info.}';
|
||||
{record? : A single watch record. Consult Wiki for more info.}
|
||||
{--tags= : The comma-separated tags to sync into the database}
|
||||
{--force : Force re-syncing even unchanged files}';
|
||||
|
||||
protected $ignored = 0;
|
||||
protected $invalid = 0;
|
||||
|
@ -65,7 +67,12 @@ class SyncMedia extends Command
|
|||
{
|
||||
$this->info('Koel syncing started. All we need now is just a little patience…');
|
||||
|
||||
Media::sync(null, $this);
|
||||
// Get the tags to sync.
|
||||
// Notice that this is only meaningful for existing records.
|
||||
// New records will have every applicable field sync'ed in.
|
||||
$tags = $this->option('tags') ? explode(',', $this->option('tags')) : [];
|
||||
|
||||
Media::sync(null, $tags, $this->option('force'), $this);
|
||||
|
||||
$this->output->writeln("<info>Completed! {$this->synced} new or updated song(s)</info>, "
|
||||
."{$this->ignored} unchanged song(s), "
|
||||
|
|
267
app/Models/File.php
Normal file
267
app/Models/File.php
Normal file
|
@ -0,0 +1,267 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Exception;
|
||||
use getID3;
|
||||
use getid3_lib;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use SplFileInfo;
|
||||
|
||||
class File
|
||||
{
|
||||
/**
|
||||
* A MD5 hash of the file's path.
|
||||
* This value is unique, and can be used to query a Song record.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $hash;
|
||||
|
||||
/**
|
||||
* The file's last modified time.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $mtime;
|
||||
|
||||
/**
|
||||
* The file's path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* The getID3 object, for ID3 tag reading.
|
||||
*
|
||||
* @var getID3
|
||||
*/
|
||||
protected $getID3;
|
||||
|
||||
/**
|
||||
* The SplFileInfo object of the file.
|
||||
*
|
||||
* @var SplFileInfo
|
||||
*/
|
||||
protected $splFileInfo;
|
||||
|
||||
/**
|
||||
* The song model that's associated with this file.
|
||||
*
|
||||
* @var Song
|
||||
*/
|
||||
protected $song;
|
||||
|
||||
/**
|
||||
* Construct our File object.
|
||||
* Upon construction,.
|
||||
*
|
||||
* @param string|SplFileInfo $path Either the file's path, or a SplFileInfo object
|
||||
* @param getID3 $getID3 A getID3 object for DI (and better performance)
|
||||
*/
|
||||
public function __construct($path, $getID3 = null)
|
||||
{
|
||||
if ($path instanceof SplFileInfo) {
|
||||
$this->splFileInfo = $path;
|
||||
} else {
|
||||
$this->splFileInfo = new SplFileInfo($path);
|
||||
}
|
||||
|
||||
$this->setGetID3($getID3);
|
||||
$this->mtime = $this->splFileInfo->getMTime();
|
||||
$this->path = $this->splFileInfo->getPathname();
|
||||
$this->hash = self::getHash($this->path);
|
||||
$this->song = Song::find($this->hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all applicable ID3 info from the file.
|
||||
*
|
||||
* @return array|void
|
||||
*/
|
||||
public function getInfo()
|
||||
{
|
||||
$info = $this->getID3->analyze($this->path);
|
||||
|
||||
if (isset($info['error'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy the available tags over to comment.
|
||||
// This is a helper from getID3, though it doesn't really work well.
|
||||
// We'll still prefer getting ID3v2 tags directly later.
|
||||
// Read on.
|
||||
getid3_lib::CopyTagsToComments($info);
|
||||
|
||||
if (!isset($info['playtime_seconds'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$track = array_get($info, 'comments.track_number', [0])[0];
|
||||
if (preg_match('#(\d+)/#', $track, $matches)) {
|
||||
$track = $matches[1];
|
||||
} elseif ((int) $track) {
|
||||
$track = (int) $track;
|
||||
}
|
||||
|
||||
$props = [
|
||||
'artist' => '',
|
||||
'album' => '',
|
||||
'title' => '',
|
||||
'length' => $info['playtime_seconds'],
|
||||
'track' => $track,
|
||||
'lyrics' => '',
|
||||
'cover' => array_get($info, 'comments.picture', [null])[0],
|
||||
'path' => $this->path,
|
||||
'mtime' => $this->mtime,
|
||||
];
|
||||
|
||||
if (!$comments = array_get($info, 'comments_html')) {
|
||||
return $props;
|
||||
}
|
||||
|
||||
// We prefer id3v2 tags over others.
|
||||
if (!$artist = array_get($info, 'tags.id3v2.artist', [null])[0]) {
|
||||
$artist = array_get($comments, 'artist', [''])[0];
|
||||
}
|
||||
|
||||
if (!$album = array_get($info, 'tags.id3v2.album', [null])[0]) {
|
||||
$album = array_get($comments, 'album', [''])[0];
|
||||
}
|
||||
|
||||
if (!$title = array_get($info, 'tags.id3v2.title', [null])[0]) {
|
||||
$title = array_get($comments, 'title', [''])[0];
|
||||
}
|
||||
|
||||
if (!$lyrics = array_get($info, 'tags.id3v2.unsynchronised_lyric', [null])[0]) {
|
||||
$lyrics = array_get($comments, 'unsynchronised_lyric', [''])[0];
|
||||
}
|
||||
|
||||
$props['artist'] = trim($artist);
|
||||
$props['album'] = trim($album);
|
||||
$props['title'] = trim($title);
|
||||
$props['lyrics'] = trim($lyrics);
|
||||
|
||||
return $this->info = $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the song with all available media info against the database.
|
||||
*
|
||||
* @param array $tags The (selective) tags to sync (if the song exists)
|
||||
* @param bool $force Whether to force syncing, even if the file is unchaged
|
||||
*
|
||||
* @return bool|Song A Song object on success,
|
||||
* true if file exists but is unmodified,
|
||||
* or false on an error.
|
||||
*/
|
||||
public function sync($tags, $force = false)
|
||||
{
|
||||
// If the file is not new or changed and we're not forcing update, don't do anything.
|
||||
if (!$this->isNewOrChanged() && !$force) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the file is invalid, don't do anything.
|
||||
if (!$info = $this->getInfo()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isChanged() || $force) {
|
||||
// This is a changed file, or the user is forcing updates.
|
||||
// We cater for the tags by removing those not specified.
|
||||
$info = array_intersect_key($info, array_flip($tags));
|
||||
|
||||
$artist = isset($info['artist']) ? Artist::get($info['artist']) : $this->song->album->artist;
|
||||
$album = isset($info['album']) ? Album::get($artist, $info['album']) : $this->song->album;
|
||||
} else {
|
||||
$album = Album::get(Artist::get($info['artist']), $info['album']);
|
||||
}
|
||||
|
||||
if (!empty($info['cover']) && !$album->has_cover) {
|
||||
try {
|
||||
$album->generateCover($info['cover']);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
}
|
||||
}
|
||||
|
||||
$info['album_id'] = $album->id;
|
||||
|
||||
unset($info['artist']);
|
||||
unset($info['album']);
|
||||
unset($info['cover']);
|
||||
|
||||
$song = Song::updateOrCreate(['id' => $this->hash], $info);
|
||||
$song->save();
|
||||
|
||||
return $song;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the file is new (its Song record can't be found in the database).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNew()
|
||||
{
|
||||
return !$this->song;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the file is changed (its Song record is found, but the timestamp is different).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isChanged()
|
||||
{
|
||||
return !$this->isNew() && $this->song->mtime !== $this->mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the file is new or changed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNewOrChanged()
|
||||
{
|
||||
return $this->isNew() || $this->isChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return getID3
|
||||
*/
|
||||
public function getGetID3()
|
||||
{
|
||||
return $this->getID3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param getID3 $getID3
|
||||
*/
|
||||
public function setGetID3($getID3 = null)
|
||||
{
|
||||
$this->getID3 = $getID3 ?: new getID3();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a unique hash from a file path.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getHash($path)
|
||||
{
|
||||
return md5(config('app.key').$path);
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ namespace App\Models;
|
|||
use App\Events\LibraryChanged;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Lastfm;
|
||||
use Media;
|
||||
|
||||
/**
|
||||
* @property string path
|
||||
|
@ -30,6 +29,7 @@ class Song extends Model
|
|||
*/
|
||||
protected $casts = [
|
||||
'length' => 'float',
|
||||
'mtime' => 'int',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -86,7 +86,7 @@ class Song extends Model
|
|||
*/
|
||||
public static function byPath($path)
|
||||
{
|
||||
return self::find(Media::getHash($path));
|
||||
return self::find(File::getHash($path));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Facades\Media;
|
||||
use App\Models\Album;
|
||||
use App\Models\Song;
|
||||
use App\Models\File;
|
||||
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
|
@ -40,7 +40,7 @@ class EventServiceProvider extends ServiceProvider
|
|||
|
||||
// Generate a unique hash for a song from its path to be the ID
|
||||
Song::creating(function ($song) {
|
||||
$song->id = Media::getHash($song->path);
|
||||
$song->id = File::getHash($song->path);
|
||||
});
|
||||
|
||||
// Remove the cover file if the album is deleted
|
||||
|
|
|
@ -5,42 +5,52 @@ namespace App\Services;
|
|||
use App\Console\Commands\SyncMedia;
|
||||
use App\Events\LibraryChanged;
|
||||
use App\Libraries\WatchRecord\WatchRecordInterface;
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use App\Models\File;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Song;
|
||||
use Exception;
|
||||
use getID3;
|
||||
use getid3_lib;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use SplFileInfo;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
class Media
|
||||
{
|
||||
/**
|
||||
* @var getID3
|
||||
* All applicable tags in a media file that we cater for.
|
||||
* Note that each isn't necessarily a valid ID3 tag name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $getID3;
|
||||
protected $allTags = ['artist', 'album', 'title', 'length', 'track', 'lyrics', 'cover', 'mtime'];
|
||||
|
||||
/**
|
||||
* Tags to be synced.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tags = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setGetID3();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the media. Oh sync the media.
|
||||
*
|
||||
* @param string|null $path
|
||||
* @param array $tags The tags to sync.
|
||||
* Only taken into account for existing records.
|
||||
* New records will have all tags synced in regardless.
|
||||
* @param bool $force Whether to force syncing even unchanged files
|
||||
* @param SyncMedia $syncCommand The SyncMedia command object, to log to console if executed by artisan.
|
||||
*/
|
||||
public function sync($path = null, SyncMedia $syncCommand = null)
|
||||
public function sync($path = null, $tags = [], $force = false, SyncMedia $syncCommand = null)
|
||||
{
|
||||
if (!app()->runningInConsole()) {
|
||||
set_time_limit(env('APP_MAX_SCAN_TIME', 600));
|
||||
}
|
||||
|
||||
$path = $path ?: Setting::get('media_path');
|
||||
$this->setTags($tags);
|
||||
|
||||
$results = [
|
||||
'good' => [], // Updated or added files
|
||||
|
@ -48,8 +58,12 @@ class Media
|
|||
'ugly' => [], // Unmodified files
|
||||
];
|
||||
|
||||
$getID3 = new getID3();
|
||||
|
||||
foreach ($this->gatherFiles($path) as $file) {
|
||||
$song = $this->syncFile($file);
|
||||
$file = new File($file, $getID3);
|
||||
|
||||
$song = $file->sync($this->tags, $force);
|
||||
|
||||
if ($song === true) {
|
||||
$results['ugly'][] = $file;
|
||||
|
@ -60,13 +74,13 @@ class Media
|
|||
}
|
||||
|
||||
if ($syncCommand) {
|
||||
$syncCommand->logToConsole($file->getPathname(), $song);
|
||||
$syncCommand->logToConsole($file->getPath(), $song);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete non-existing songs.
|
||||
$hashes = array_map(function ($f) {
|
||||
return $this->getHash($f->getPathname());
|
||||
return self::getHash($f->getPath());
|
||||
}, array_merge($results['ugly'], $results['good']));
|
||||
|
||||
Song::whereNotIn('id', $hashes)->delete();
|
||||
|
@ -87,52 +101,6 @@ class Media
|
|||
return Finder::create()->files()->name('/\.(mp3|ogg|m4a|flac)$/i')->in($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a song with all available media info against the database.
|
||||
*
|
||||
* @param SplFileInfo|string $file The SplFileInfo instance of the file, or the file path.
|
||||
*
|
||||
* @return bool|Song A Song object on success,
|
||||
* true if file exists but is unmodified,
|
||||
* or false on an error.
|
||||
*/
|
||||
public function syncFile($file)
|
||||
{
|
||||
if (!($file instanceof SplFileInfo)) {
|
||||
$file = new SplFileInfo($file);
|
||||
}
|
||||
|
||||
if (!$info = $this->getInfo($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->isNewOrChanged($file)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$artist = Artist::get($info['artist']);
|
||||
$album = Album::get($artist, $info['album']);
|
||||
|
||||
if ($info['cover'] && !$album->has_cover) {
|
||||
try {
|
||||
$album->generateCover($info['cover']);
|
||||
} catch (Exception $e) {
|
||||
Log::error($e);
|
||||
}
|
||||
}
|
||||
|
||||
$info['album_id'] = $album->id;
|
||||
|
||||
unset($info['artist']);
|
||||
unset($info['album']);
|
||||
unset($info['cover']);
|
||||
|
||||
$song = Song::updateOrCreate(['id' => $this->getHash($file->getPathname())], $info);
|
||||
$song->save();
|
||||
|
||||
return $song;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync media using a watch record.
|
||||
*
|
||||
|
@ -163,7 +131,8 @@ class Media
|
|||
// Otherwise, it's a new or changed file. Try to sync it in.
|
||||
// File format etc. will be handled by the syncFile method.
|
||||
elseif ($record->isNewOrModified()) {
|
||||
Log::info($this->syncFile($path) instanceof Song ? "Synchronized $path" : "Invalid file $path");
|
||||
$result = (new File($path))->sync($this->tags);
|
||||
Log::info($result instanceof Song ? "Synchronized $path" : "Invalid file $path");
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -182,7 +151,7 @@ class Media
|
|||
}
|
||||
} elseif ($record->isNewOrModified()) {
|
||||
foreach ($this->gatherFiles($path) as $file) {
|
||||
$this->syncFile($file);
|
||||
(new File($file))->sync($this->tags);
|
||||
}
|
||||
|
||||
Log::info("Synced all song(s) under $path");
|
||||
|
@ -190,91 +159,22 @@ class Media
|
|||
}
|
||||
|
||||
/**
|
||||
* Check if a media file is new or changed.
|
||||
* A file is considered existing and unchanged only when:
|
||||
* - its hash (ID) can be found in the database, and
|
||||
* - its last modified time is the same with that of the comparing file.
|
||||
* Construct an array of tags to be synced into the database from an input array of tags.
|
||||
* If the input array is empty or contains only invalid items, we use all tags.
|
||||
* Otherwise, we only use the valid items it it.
|
||||
*
|
||||
* @param SplFileInfo $file
|
||||
* @param array $tags
|
||||
*
|
||||
* @return bool
|
||||
* @return array
|
||||
*/
|
||||
protected function isNewOrChanged(SplFileInfo $file)
|
||||
public function setTags($tags = [])
|
||||
{
|
||||
return !Song::whereIdAndMtime($this->getHash($file->getPathname()), $file->getMTime())->count();
|
||||
$this->tags = array_intersect((array) $tags, $this->allTags) ?: $this->allTags;
|
||||
|
||||
// We always keep track of mtime.
|
||||
if (!in_array('mtime', $this->tags)) {
|
||||
$this->tags[] = 'mtime';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ID3 info from a file.
|
||||
*
|
||||
* @param SplFileInfo $file
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getInfo(SplFileInfo $file)
|
||||
{
|
||||
$info = $this->getID3->analyze($file->getPathname());
|
||||
|
||||
if (isset($info['error'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy the available tags over to comment.
|
||||
// This is a helper from getID3, though it doesn't really work well.
|
||||
// We'll still prefer getting ID3v2 tags directly later.
|
||||
// Read on.
|
||||
getid3_lib::CopyTagsToComments($info);
|
||||
|
||||
if (!isset($info['playtime_seconds'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$track = array_get($info, 'comments.track_number', [0])[0];
|
||||
if (preg_match('#(\d+)/#', $track, $matches)) {
|
||||
$track = $matches[1];
|
||||
} elseif ((int) $track) {
|
||||
$track = (int) $track;
|
||||
}
|
||||
|
||||
$props = [
|
||||
'artist' => '',
|
||||
'album' => '',
|
||||
'title' => '',
|
||||
'length' => $info['playtime_seconds'],
|
||||
'track' => $track,
|
||||
'lyrics' => '',
|
||||
'cover' => array_get($info, 'comments.picture', [null])[0],
|
||||
'path' => $file->getPathname(),
|
||||
'mtime' => $file->getMTime(),
|
||||
];
|
||||
|
||||
if (!$comments = array_get($info, 'comments_html')) {
|
||||
return $props;
|
||||
}
|
||||
|
||||
// We prefer id3v2 tags over others.
|
||||
if (!$artist = array_get($info, 'tags.id3v2.artist', [null])[0]) {
|
||||
$artist = array_get($comments, 'artist', [''])[0];
|
||||
}
|
||||
|
||||
if (!$album = array_get($info, 'tags.id3v2.album', [null])[0]) {
|
||||
$album = array_get($comments, 'album', [''])[0];
|
||||
}
|
||||
|
||||
if (!$title = array_get($info, 'tags.id3v2.title', [null])[0]) {
|
||||
$title = array_get($comments, 'title', [''])[0];
|
||||
}
|
||||
|
||||
if (!$lyrics = array_get($info, 'tags.id3v2.unsynchronised_lyric', [null])[0]) {
|
||||
$lyrics = array_get($comments, 'unsynchronised_lyric', [''])[0];
|
||||
}
|
||||
|
||||
$props['artist'] = trim($artist);
|
||||
$props['album'] = trim($album);
|
||||
$props['title'] = trim($title);
|
||||
$props['lyrics'] = trim($lyrics);
|
||||
|
||||
return $props;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -286,22 +186,6 @@ class Media
|
|||
*/
|
||||
public function getHash($path)
|
||||
{
|
||||
return md5(config('app.key').$path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return getID3
|
||||
*/
|
||||
public function getGetID3()
|
||||
{
|
||||
return $this->getID3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param getID3 $getID3
|
||||
*/
|
||||
public function setGetID3($getID3 = null)
|
||||
{
|
||||
$this->getID3 = $getID3 ?: new getID3();
|
||||
return File::getHash($path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,66 @@ class MediaTest extends TestCase
|
|||
$this->assertEquals($currentCover, Album::find($album->id)->cover);
|
||||
}
|
||||
|
||||
public function testForceSync()
|
||||
{
|
||||
$this->expectsEvents(LibraryChanged::class);
|
||||
|
||||
$media = new Media();
|
||||
$media->sync($this->mediaPath);
|
||||
|
||||
// Make some modification to the records
|
||||
$song = Song::orderBy('id', 'desc')->first();
|
||||
$orginalTitle = $song->title;
|
||||
$orginalLyrics = $song->lyrics;
|
||||
|
||||
$song->update([
|
||||
'title' => "It's John Cena!",
|
||||
'lyrics' => 'Booom Wroooom',
|
||||
]);
|
||||
|
||||
// Resync without forcing
|
||||
$media->sync($this->mediaPath);
|
||||
|
||||
// Validate that the changes are not lost
|
||||
$song = Song::orderBy('id', 'desc')->first();
|
||||
$this->assertEquals("It's John Cena!", $song->title);
|
||||
$this->assertEquals('Booom Wroooom', $song->lyrics);
|
||||
|
||||
// Resync with force
|
||||
$media->sync($this->mediaPath, [], true);
|
||||
|
||||
// All is lost.
|
||||
$song = Song::orderBy('id', 'desc')->first();
|
||||
$this->assertEquals($orginalTitle, $song->title);
|
||||
$this->assertEquals($orginalLyrics, $song->lyrics);
|
||||
}
|
||||
|
||||
public function testSyncSelectiveTags()
|
||||
{
|
||||
$this->expectsEvents(LibraryChanged::class);
|
||||
|
||||
$media = new Media();
|
||||
$media->sync($this->mediaPath);
|
||||
|
||||
// Make some modification to the records
|
||||
$song = Song::orderBy('id', 'desc')->first();
|
||||
$orginalTitle = $song->title;
|
||||
$orginalLyrics = $song->lyrics;
|
||||
|
||||
$song->update([
|
||||
'title' => "It's John Cena!",
|
||||
'lyrics' => 'Booom Wroooom',
|
||||
]);
|
||||
|
||||
// Sync only the selective tags
|
||||
$media->sync($this->mediaPath, ['title'], true);
|
||||
|
||||
// Validate that the specified tags are changed, other remains the same
|
||||
$song = Song::orderBy('id', 'desc')->first();
|
||||
$this->assertEquals($orginalTitle, $song->title);
|
||||
$this->assertEquals('Booom Wroooom', $song->lyrics);
|
||||
}
|
||||
|
||||
public function testWatchSingleFileAdded()
|
||||
{
|
||||
$path = $this->mediaPath.'/blank.mp3';
|
||||
|
|
Loading…
Reference in a new issue