feat: video priority attribute (#1079)

This commit is contained in:
Kyrch
2026-02-04 13:24:03 -03:00
committed by GitHub
parent 742dc20106
commit af2946155e
12 changed files with 121 additions and 83 deletions
@@ -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();
}
/**
-1
View File
@@ -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(),
+8 -5
View File
@@ -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
+3 -2
View File
@@ -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);
}
/**
+8 -5
View File
@@ -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;
});
}
/**
+10 -7
View File
@@ -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
View File
@@ -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);
}
/**
+9 -6
View File
@@ -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
+1 -1
View File
@@ -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();