mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 01:24:46 +02:00
feat: video priority attribute (#1079)
This commit is contained in:
@@ -128,7 +128,7 @@ class BackfillAudioAction extends BackfillAction
|
||||
{
|
||||
// Allow bypassing of source video derivation
|
||||
if ($this->replaceRelatedAudio()) {
|
||||
$sourceVideo = $this->getSourceVideo('<');
|
||||
$sourceVideo = $this->getSourceVideo(false);
|
||||
} elseif ($this->deriveSourceVideo()) {
|
||||
$sourceVideo = $this->getSourceVideo();
|
||||
} else {
|
||||
@@ -177,23 +177,12 @@ class BackfillAudioAction extends BackfillAction
|
||||
return $audio;
|
||||
}
|
||||
|
||||
protected function getSourceVideo(string $operation = '>'): ?Video
|
||||
protected function getSourceVideo(bool $descending = true): ?Video
|
||||
{
|
||||
$source = null;
|
||||
|
||||
$sourceCandidates = $this->getAdjacentVideos();
|
||||
|
||||
foreach ($sourceCandidates as $sourceCandidate) {
|
||||
if ($operation === '>' && (! $source instanceof Video || $sourceCandidate->getSourcePriority() > $source->getSourcePriority())) {
|
||||
$source = $sourceCandidate;
|
||||
}
|
||||
|
||||
if ($operation === '<' && (! $source instanceof Video || $sourceCandidate->getSourcePriority() < $source->getSourcePriority())) {
|
||||
$source = $sourceCandidate;
|
||||
}
|
||||
}
|
||||
|
||||
return $source;
|
||||
return $this->getAdjacentVideos()->sortBy(
|
||||
fn (Video $video) => $video->getAttribute(Video::ATTRIBUTE_PRIORITY),
|
||||
descending: $descending
|
||||
)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,6 @@ enum VideoSource: int implements HasLabel
|
||||
|
||||
/**
|
||||
* Score sources to help prioritize videos.
|
||||
* TODO: This should be refactored into attributes.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\GraphQL\Schema\Fields\Wiki\Video;
|
||||
|
||||
use App\Contracts\GraphQL\Fields\DisplayableField;
|
||||
use App\GraphQL\Schema\Fields\Field;
|
||||
use App\Models\Wiki\Video;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
class VideoPriorityField extends Field implements DisplayableField
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(Video::ATTRIBUTE_PRIORITY, nullable: false);
|
||||
}
|
||||
|
||||
public function description(): string
|
||||
{
|
||||
return 'The priority value for the video';
|
||||
}
|
||||
|
||||
public function baseType(): Type
|
||||
{
|
||||
return Type::int();
|
||||
}
|
||||
|
||||
public function canBeDisplayed(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ use App\GraphQL\Schema\Fields\Wiki\Video\VideoMimetypeField;
|
||||
use App\GraphQL\Schema\Fields\Wiki\Video\VideoNcField;
|
||||
use App\GraphQL\Schema\Fields\Wiki\Video\VideoOverlapField;
|
||||
use App\GraphQL\Schema\Fields\Wiki\Video\VideoPathField;
|
||||
use App\GraphQL\Schema\Fields\Wiki\Video\VideoPriorityField;
|
||||
use App\GraphQL\Schema\Fields\Wiki\Video\VideoResolutionField;
|
||||
use App\GraphQL\Schema\Fields\Wiki\Video\VideoSizeField;
|
||||
use App\GraphQL\Schema\Fields\Wiki\Video\VideoSourceField;
|
||||
@@ -59,6 +60,7 @@ class VideoType extends EloquentType implements SubmitableType
|
||||
new VideoOverlapField(),
|
||||
new LocalizedEnumField(new VideoOverlapField()),
|
||||
new VideoPathField(),
|
||||
new VideoPriorityField(),
|
||||
new VideoResolutionField(),
|
||||
new VideoSizeField(),
|
||||
new VideoSourceField(),
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Models\BaseModel;
|
||||
use Database\Factories\Admin\DumpFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Scope;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
/**
|
||||
@@ -81,13 +82,15 @@ class Dump extends BaseModel
|
||||
/**
|
||||
* The link of the dump.
|
||||
*/
|
||||
protected function getLinkAttribute(): ?string
|
||||
protected function link(): Attribute
|
||||
{
|
||||
if ($this->hasAttribute($this->getRouteKeyName()) && $this->exists) {
|
||||
return route('dump.show', $this);
|
||||
}
|
||||
return Attribute::make(function (): ?string {
|
||||
if ($this->hasAttribute($this->getRouteKeyName()) && $this->exists) {
|
||||
return route('dump.show', $this);
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace App\Models\User;
|
||||
|
||||
use App\Models\List\ExternalProfile;
|
||||
use Database\Factories\User\NotificationFactory;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Notifications\DatabaseNotification;
|
||||
@@ -33,9 +34,9 @@ class Notification extends DatabaseNotification
|
||||
/**
|
||||
* Virtual attribute to use in relations.
|
||||
*/
|
||||
protected function getProfileIdAttribute(): ?int
|
||||
protected function profileId(): Attribute
|
||||
{
|
||||
return Arr::get($this->getAttribute(self::ATTRIBUTE_DATA), 'profileId');
|
||||
return Attribute::make(fn () => Arr::get($this->getAttribute(self::ATTRIBUTE_DATA), 'profileId'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\Auth\User;
|
||||
use App\Models\BaseModel;
|
||||
use App\Observers\User\Submission\SubmissionVirtualObserver;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
@@ -75,12 +76,9 @@ class SubmissionVirtual extends BaseModel
|
||||
return strval($this->getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return class-string<Model>
|
||||
*/
|
||||
protected function getModelAttribute(): string
|
||||
protected function model(): Attribute
|
||||
{
|
||||
return Relation::getMorphedModel($this->model_type) ?? $this->model_type;
|
||||
return Attribute::make(fn () => Relation::getMorphedModel($this->model_type) ?? $this->model_type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@ use App\Events\Wiki\Audio\AudioRestored;
|
||||
use App\Events\Wiki\Audio\AudioUpdated;
|
||||
use App\Models\BaseModel;
|
||||
use Database\Factories\Wiki\AudioFactory;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -118,13 +119,15 @@ class Audio extends BaseModel implements Auditable, HasAggregateViews, SoftDelet
|
||||
];
|
||||
}
|
||||
|
||||
protected function getLinkAttribute(): ?string
|
||||
protected function link(): Attribute
|
||||
{
|
||||
if ($this->hasAttribute($this->getRouteKeyName()) && $this->exists) {
|
||||
return route('audio.show', $this);
|
||||
}
|
||||
return Attribute::make(function (): ?string {
|
||||
if ($this->hasAttribute($this->getRouteKeyName()) && $this->exists) {
|
||||
return route('audio.show', $this);
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@ use App\Models\BaseModel;
|
||||
use App\Models\List\Playlist;
|
||||
use App\Pivots\Morph\Imageable;
|
||||
use Database\Factories\Wiki\ImageFactory;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -128,16 +129,18 @@ class Image extends BaseModel implements Auditable, SoftDeletable
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
protected function getLinkAttribute(): ?string
|
||||
protected function link(): Attribute
|
||||
{
|
||||
if ($this->hasAttribute(Image::ATTRIBUTE_PATH) && $this->exists) {
|
||||
/** @var \Illuminate\Filesystem\FilesystemAdapter $fs */
|
||||
$fs = Storage::disk(Config::get('image.disk'));
|
||||
return Attribute::make(function () {
|
||||
if ($this->hasAttribute(Image::ATTRIBUTE_PATH) && $this->exists) {
|
||||
/** @var \Illuminate\Filesystem\FilesystemAdapter $fs */
|
||||
$fs = Storage::disk(Config::get('image.disk'));
|
||||
|
||||
return $fs->url($this->getAttribute(Image::ATTRIBUTE_PATH));
|
||||
}
|
||||
return $fs->url($this->getAttribute(Image::ATTRIBUTE_PATH));
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+38
-34
@@ -28,6 +28,7 @@ use App\Pivots\Wiki\AnimeThemeEntryVideo;
|
||||
use Database\Factories\Wiki\VideoFactory;
|
||||
use Elastic\ScoutDriverPlus\Searchable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
@@ -49,6 +50,7 @@ use OwenIt\Auditing\Contracts\Auditable;
|
||||
* @property bool $nc
|
||||
* @property VideoOverlap $overlap
|
||||
* @property string $path
|
||||
* @property int $priority
|
||||
* @property Collection<int, PlaylistTrack> $tracks
|
||||
* @property int|null $resolution
|
||||
* @property VideoScript|null $videoscript
|
||||
@@ -82,6 +84,7 @@ class Video extends BaseModel implements Auditable, HasAggregateViews, SoftDelet
|
||||
final public const string ATTRIBUTE_NC = 'nc';
|
||||
final public const string ATTRIBUTE_OVERLAP = 'overlap';
|
||||
final public const string ATTRIBUTE_PATH = 'path';
|
||||
final public const string ATTRIBUTE_PRIORITY = 'priority';
|
||||
final public const string ATTRIBUTE_RESOLUTION = 'resolution';
|
||||
final public const string ATTRIBUTE_SIZE = 'size';
|
||||
final public const string ATTRIBUTE_SOURCE = 'source';
|
||||
@@ -156,6 +159,7 @@ class Video extends BaseModel implements Auditable, HasAggregateViews, SoftDelet
|
||||
*/
|
||||
protected $appends = [
|
||||
Video::ATTRIBUTE_LINK,
|
||||
Video::ATTRIBUTE_PRIORITY,
|
||||
Video::ATTRIBUTE_TAGS,
|
||||
];
|
||||
|
||||
@@ -177,21 +181,47 @@ class Video extends BaseModel implements Auditable, HasAggregateViews, SoftDelet
|
||||
];
|
||||
}
|
||||
|
||||
protected function getLinkAttribute(): ?string
|
||||
protected function link(): Attribute
|
||||
{
|
||||
if ($this->hasAttribute($this->getRouteKeyName()) && $this->exists) {
|
||||
return route('video.show', $this);
|
||||
return Attribute::make(function (): ?string {
|
||||
if ($this->hasAttribute($this->getRouteKeyName()) && $this->exists) {
|
||||
return route('video.show', $this);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the priority score for the video.
|
||||
* Higher scores increase the likelihood of the video to be the source of an audio track.
|
||||
*/
|
||||
protected function priority(): Attribute
|
||||
{
|
||||
$priority = intval($this->source?->getPriority());
|
||||
|
||||
// Videos that play over the episode will likely have compressed audio
|
||||
if ($this->overlap === VideoOverlap::OVER) {
|
||||
$priority -= 8;
|
||||
}
|
||||
|
||||
return null;
|
||||
// Videos that transition to or from the episode may have compressed audio
|
||||
if ($this->overlap === VideoOverlap::TRANS) {
|
||||
$priority -= 5;
|
||||
}
|
||||
|
||||
// De-prioritize hardsubbed videos
|
||||
if ($this->lyrics || $this->subbed) {
|
||||
$priority--;
|
||||
}
|
||||
|
||||
return Attribute::make(fn (): int => $priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* The array of tags used to uniquely identify the video within the context of a theme.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getTagsAttribute(): array
|
||||
protected function tags(): Attribute
|
||||
{
|
||||
$tags = [];
|
||||
|
||||
@@ -211,33 +241,7 @@ class Video extends BaseModel implements Auditable, HasAggregateViews, SoftDelet
|
||||
$tags[] = 'Lyrics';
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the priority score for the video.
|
||||
* Higher scores increase the likelihood of the video to be the source of an audio track.
|
||||
*/
|
||||
public function getSourcePriority(): int
|
||||
{
|
||||
$priority = intval($this->source?->getPriority());
|
||||
|
||||
// Videos that play over the episode will likely have compressed audio
|
||||
if ($this->overlap === VideoOverlap::OVER) {
|
||||
$priority -= 8;
|
||||
}
|
||||
|
||||
// Videos that transition to or from the episode may have compressed audio
|
||||
if ($this->overlap === VideoOverlap::TRANS) {
|
||||
$priority -= 5;
|
||||
}
|
||||
|
||||
// De-prioritize hardsubbed videos
|
||||
if ($this->lyrics || $this->subbed) {
|
||||
$priority--;
|
||||
}
|
||||
|
||||
return $priority;
|
||||
return Attribute::make(fn (): array => $tags);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ use App\Http\Api\Schema\Wiki\Video\ScriptSchema;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\Wiki\Video;
|
||||
use Database\Factories\Wiki\Video\VideoScriptFactory;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use OwenIt\Auditing\Auditable as HasAudits;
|
||||
@@ -93,14 +94,16 @@ class VideoScript extends BaseModel implements Auditable, InteractsWithSchema, S
|
||||
VideoScript::ATTRIBUTE_LINK,
|
||||
];
|
||||
|
||||
protected function getLinkAttribute(): ?string
|
||||
protected function link(): Attribute
|
||||
{
|
||||
// Necessary for 'make' factories.
|
||||
if ($this->hasAttribute(VideoScript::ATTRIBUTE_ID)) {
|
||||
return route('videoscript.show', $this);
|
||||
}
|
||||
return Attribute::make(function (): ?string {
|
||||
// Necessary for 'make' factories.
|
||||
if ($this->hasAttribute(VideoScript::ATTRIBUTE_ID)) {
|
||||
return route('videoscript.show', $this);
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
|
||||
@@ -165,7 +165,7 @@ test('source priority', function (array $a, array $b) {
|
||||
|
||||
$second = Video::factory()->createOne($b);
|
||||
|
||||
$this->assertGreaterThan($first->getSourcePriority(), $second->getSourcePriority());
|
||||
$this->assertGreaterThan($first->getAttribute(Video::ATTRIBUTE_PRIORITY), $second->getAttribute(Video::ATTRIBUTE_PRIORITY));
|
||||
})->with('priorityProvider');
|
||||
test('entries', function () {
|
||||
$entryCount = fake()->randomDigitNotNull();
|
||||
|
||||
Reference in New Issue
Block a user