goon/alembic/versions/20260820_0031_duration_backfill_queue.py
goon-foss 544de9c5ba feat(ingest): backfill brakujacej dlugosci scen
1 755 421 scen nie ma dlugosci ani na scenie, ani na zrodle, wiec wypadaja z
feedu: w SQL "duration_sec >= 60" nie jest prawda dla NULL, przez co scena o
nieznanej dlugosci znika tak samo jak dziesieciosekundowy smiec. Filtr zostaje,
bo odcina smieci, uzupelniamy dane.

Swiadomie waskie originy. Uniwersalny regex trafia w smieci: porntrex ma w HTML
"duration: 5" reklamy, mypornerleak dlugosc ciastka 720, a xhamster i porntrex
wypisuja dlugosci filmow z paska obok (naiwne dopasowanie dalo 198s zamiast 240s).
Zla dlugosc jest gorsza niz brak, bo dedup opiera sie na niej najmocniej.

- xvideos, xnxx: VideoObject LD-JSON, 5/5 na probce
- xhamster: videoEntity z kontrola zgodnosci id z URL-a
- porntrex pominiety: strona szczegolow nie podaje wlasnej dlugosci

Kolejka jak przy phashu (plan raz, mielenie paczkami, przezywa restart):
845 899 scen, 500 stron co 5 min, czyli ok. 6 dni. Plan powtarza sie co 24h,
zeby swieze sceny bez dlugosci tez trafialy do kolejki.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 12:10:22 +02:00

55 lines
1.9 KiB
Python

"""duration_backfill_queue: kolejka scen do doliczenia dlugosci ze strony tuba
Uzasadnienie i liczby: `app/models/duration_backfill_queue.py`.
Revision ID: 0031_duration_backfill
Revises: 0030_scenes_quality_idx
Create Date: 2026-08-20
"""
from __future__ import annotations
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql
# Uwaga: `alembic_version.version_num` to varchar(32), dluzsze id nie wejdzie.
revision: str = "0031_duration_backfill"
down_revision: str | None = "0030_scenes_quality_idx"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table(
"duration_backfill_queue",
sa.Column("scene_id", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("page_url", sa.String(length=1024), nullable=False),
sa.Column("origin", sa.String(length=64), nullable=False),
sa.Column("attempts", sa.Integer(), nullable=False, server_default="0"),
sa.Column("done_at", sa.DateTime(timezone=True), nullable=True),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.ForeignKeyConstraint(
["scene_id"], ["scenes.id"], ondelete="CASCADE",
name="fk_duration_backfill_queue_scene_id_scenes",
),
sa.PrimaryKeyConstraint("scene_id", name="pk_duration_backfill_queue"),
)
# Paczka bierze wiersze niezrobione, po originie (zeby nie zawalic jednego hosta).
op.create_index(
"ix_duration_backfill_queue_pending",
"duration_backfill_queue",
["origin", "attempts"],
postgresql_where=sa.text("done_at IS NULL"),
)
def downgrade() -> None:
op.drop_index(
"ix_duration_backfill_queue_pending", table_name="duration_backfill_queue"
)
op.drop_table("duration_backfill_queue")