/** * Sync a song with all available media info against the database. * * @param SplFileInfo $file The SplFileInfo instance of the file. * * @return bool|Song A Song object on success, * true if file existing but unmodified, * or false on error. */ public function syncFile(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; }
public function testNameWithWeirdCharacters() { // Don't really think this is even necessary if the user has set a proper utf8 encoding // for the database. $name = '��Ой°Ы&囧rz'; $artist = factory(Artist::class)->create(['name' => $name]); $this->assertEquals($artist->id, Artist::get($name)->id); }
public function testUtf16Names() { $name = file_get_contents(__DIR__ . '/blobs/utf16'); $artist = Artist::get($name); $artist = Artist::get($name); // to make sure there's no constraint exception $this->assertEquals($artist->id, Artist::get($name)->id); }
/** * Store a new song or update an existing one with data from AWS. * * @param PutSongRequest $request * * @return \Illuminate\Http\JsonResponse */ public function put(PutSongRequest $request) { $path = "s3://{$request->bucket}/{$request->key}"; $tags = $request->tags; $artist = Artist::get(array_get($tags, 'artist')); $compilation = (bool) trim(array_get($tags, 'albumartist')); $album = Album::get($artist, array_get($tags, 'album'), $compilation); if ($cover = array_get($tags, 'cover')) { $album->writeCoverFile(base64_decode($cover['data']), $cover['extension']); } $song = Song::updateOrCreate(['id' => Media::getHash($path)], ['path' => $path, 'album_id' => $album->id, 'contributing_artist_id' => $compilation ? $artist->id : null, 'title' => trim(array_get($tags, 'title', '')), 'length' => array_get($tags, 'duration', 0), 'track' => intval(array_get($tags, 'track')), 'lyrics' => array_get($tags, 'lyrics', ''), 'mtime' => time()]); return response()->json($song); }
/** * Update a single song's info. * * @param string $title * @param string $albumName * @param string $artistName * @param string $lyrics * @param int $track * @param int $compilationState * * @return self */ public function updateSingle($title, $albumName, $artistName, $lyrics, $track, $compilationState) { // If the artist name is "Various Artists", it's a compilation song no matter what. if ($artistName === Artist::VARIOUS_NAME) { $compilationState = 1; } // If the complitation state is "no change," we determine it via the current // "contributing_artist_id" field value. if ($compilationState === 2) { $compilationState = $this->contributing_artist_id ? 1 : 0; } $album = null; if ($compilationState === 0) { // Not a compilation song $this->contributing_artist_id = null; $albumArtist = Artist::get($artistName); $album = Album::get($albumArtist, $albumName, false); } else { $contributingArtist = Artist::get($artistName); $this->contributing_artist_id = $contributingArtist->id; $album = Album::get(Artist::getVarious(), $albumName, true); } $this->album_id = $album->id; $this->lyrics = $lyrics; $this->track = $track; $this->save(); // Get the updated record, with album and all. $updatedSong = self::with('album', 'album.artist', 'contributingArtist')->find($this->id); // Make sure lyrics is included in the returned JSON. $updatedSong->makeVisible('lyrics'); return $updatedSong; }
/** * 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 unchanged * * @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; } // Fixes #366. If the file is new, we use all tags by simply setting $force to false. if ($this->isNew()) { $force = false; } $artist = null; if ($this->isChanged() || $force) { // This is a changed file, or the user is forcing updates. // In such a case, the user must have specified a list of tags to sync. // A sample command could be: ./artisan koel:sync --force --tags=artist,album,lyrics // We cater for these tags by removing those not specified. // There's a special case with 'album' though. // If 'compilation' tag is specified, 'album' must be counted in as well. // But if 'album' isn't specified, we don't want to update normal albums. // This variable is to keep track of this state. $changeCompilationAlbumOnly = false; if (in_array('compilation', $tags, true) && !in_array('album', $tags, true)) { $tags[] = 'album'; $changeCompilationAlbumOnly = true; } $info = array_intersect_key($info, array_flip($tags)); // If the "artist" tag is specified, use it. // Otherwise, re-use the existing model value. $artist = isset($info['artist']) ? Artist::get($info['artist']) : $this->song->album->artist; $isCompilation = (bool) array_get($info, 'compilation'); // If the "album" tag is specified, use it. // Otherwise, re-use the existing model value. if (isset($info['album'])) { $album = $changeCompilationAlbumOnly ? $this->song->album : Album::get($artist, $info['album'], $isCompilation); } else { $album = $this->song->album; } } else { // The file is newly added. $isCompilation = (bool) array_get($info, 'compilation'); $artist = Artist::get($info['artist']); $album = Album::get($artist, $info['album'], $isCompilation); } if (!$album->has_cover) { // If the album has no cover, we try to get the cover image from existing tag data if (!empty($info['cover'])) { try { $album->generateCover($info['cover']); } catch (Exception $e) { Log::error($e); } } elseif ($cover = $this->getCoverFileUnderSameDirectory()) { $album->copyCoverFile($cover); } } $info['album_id'] = $album->id; // If the song is part of a compilation, make sure we properly set its // artist and contributing artist attributes. if ($isCompilation) { $info['contributing_artist_id'] = $artist->id; } // Remove these values from the info array, so that we can just use the array as model's input data. array_forget($info, ['artist', 'albumartist', 'album', 'cover', 'compilation']); return Song::updateOrCreate(['id' => $this->hash], $info); }
/** * 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; // Remove these values from the info array, so that we can just use the array as model's input data. array_forget($info, ['artist', 'album', 'cover']); $song = Song::updateOrCreate(['id' => $this->hash], $info); $song->save(); return $song; }
/** * Update song info. * * @param array $ids * @param array $data The data array, with these supported fields: * - title * - artistName * - albumName * - lyrics * All of these are optional, in which case the info will not be changed * (except for lyrics, which will be emptied). * * @return */ public static function updateInfo($ids, $data) { /* * The artist that our songs will be associated to. * If they are not existing yet, we will create the object. * * @var Artist */ $targetArtist = null; /* * The album that our songs will be associated to. * If it can't be found, we'll create it. * * @var Album */ $targetAlbum = null; /* * An array of the updated songs. * * @var array */ $updatedSongs = []; foreach ((array) $ids as $id) { if (!($song = self::with('album', 'album.artist')->find($id))) { continue; } // If we're updating only one song, take into account the title, lyrics, and track number. if (count($ids) === 1) { $song->title = trim($data['title']) ?: $song->title; $song->lyrics = trim($data['lyrics']); $song->track = (int) trim($data['track']); } // If "newArtist" is provided, we'll see if such an artist name is found in our database. // If negative, we create a new record into $targetArtist. if ($artistName = trim($data['artistName'])) { $targetArtist = Artist::get($artistName); } else { $targetArtist = $song->album->artist; } // Here it gets a little tricky. // If "newAlbum" is provided, we find the album OF THE ARTIST. // If none is found, create it as $targetAlbum, which is also populated just once. if ($albumName = trim($data['albumName'])) { $targetAlbum = Album::get($targetArtist, $albumName); $song->album_id = $targetAlbum->id; } else { // The albumName is empty. // However, if the artist has changed, it's not the same album anymore. // Instead, the song now belongs to another album WITH THE SAME NAME, but under the new artist. // // See? I told you, it's tricky. // Or maybe it's not. // Whatever. if ($targetArtist->id !== $song->album->artist->id) { $song->album_id = Album::get($targetArtist, $song->album->name)->id; } } $song->save(); // Get the updated record, with album and all. $updatedSong = self::with('album', 'album.artist')->find($id); // Make sure lyrics is included in the returned JSON. $updatedSong->makeVisible('lyrics'); $updatedSongs[] = $updatedSong; } // Our library may have been changed. Broadcast an event to tidy it up if need be. if ($updatedSongs) { event(new LibraryChanged()); } return $updatedSongs; }
/** * 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; } $isCompilation = false; $artist = null; if ($this->isChanged() || $force) { // This is a changed file, or the user is forcing updates. // In such a case, the user must have specified a list of tags to sync. // A sample command could be: ./artisan koel:sync --force --tags=artist,album,lyrics // We cater for these tags by removing those not specified. if (in_array('part_of_a_compilation', $tags) && !in_array('album', $tags)) { // If 'part_of_a_compilation' tag is specified, 'album' must be counted in as well. $tags[] = 'album'; } $info = array_intersect_key($info, array_flip($tags)); // If the "artist" tag is specified, use it. // Otherwise, re-use the existing model value. $artist = isset($info['artist']) ? Artist::get($info['artist']) : $this->song->album->artist; $isCompilation = (bool) array_get($info, 'part_of_a_compilation'); // If the "album" tag is specified, use it. // Otherwise, re-use the existing model value. $album = isset($info['album']) ? Album::get($artist, $info['album'], $isCompilation) : $this->song->album; } else { // The file is newly added. $isCompilation = (bool) array_get($info, 'part_of_a_compilation'); $artist = Artist::get($info['artist']); $album = Album::get($artist, $info['album'], $isCompilation); } if (!empty($info['cover']) && !$album->has_cover) { try { $album->generateCover($info['cover']); } catch (Exception $e) { Log::error($e); } } $info['album_id'] = $album->id; // If the song is part of a compilation, make sure we properly set its // artist and contributing artist attributes. if ($isCompilation) { $info['contributing_artist_id'] = $artist->id; } // Remove these values from the info array, so that we can just use the array as model's input data. array_forget($info, ['artist', 'album', 'cover', 'part_of_a_compilation']); $song = Song::updateOrCreate(['id' => $this->hash], $info); $song->save(); return $song; }