fix(downloads): a signed-in Facebook reel keeps its title, and an mp3 stays an mp3
Since a Facebook cookie was added on prod, every reel came down titled "Video": measured on dev, a signed-in read gives yt-dlp the placeholder "Video" (an 18+ reel read as Chrome gives "Facebook") while the post's text is still in the description. A new SourceAdapter.title hook takes the whole first line of the description in that case, and both title writes (the early fill and the finished download) go through it, so the row, the file name and the share preview carry the post's text again. The line is not cut with an ellipsis: normalize_title's NFKC turns "…" into "..." and collapses it to ".", which read as a finished sentence (seen in dev UAT). The "Audio (MP3)" profile's extracted .mp3 was remuxed into an .mp4 by the browser-safe step (the audio spec is clamped to container mp4 too) and served as video/mp4, which iPad's WebKit will not play (prod job 268). Audio mode now skips that step, so an .mp3 is served as audio/mpeg and an .m4a as audio/mp4. Audio downloads version their cache identity on their own (v5), so a cached remuxed audio re-derives without re-downloading every cached video.
This commit is contained in:
@@ -314,3 +314,89 @@ def test_0084_backfills_only_the_facebook_parse_failures_it_measured(db):
|
||||
finally:
|
||||
if not upgraded:
|
||||
command.upgrade(cfg, "head")
|
||||
|
||||
|
||||
def _ffmpeg_media(path, *codec_args):
|
||||
subprocess.run(
|
||||
["ffmpeg", "-y", "-loglevel", "error", "-f", "lavfi", "-i", "sine=frequency=440"]
|
||||
+ list(codec_args)
|
||||
+ ["-t", "2", str(path)],
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("ffmpeg") is None, reason="ffmpeg not available")
|
||||
def test_a_signed_in_facebook_download_is_titled_with_its_posts_text(db, monkeypatch, tmp_path):
|
||||
# Prod jobs 265–268 (2026-09-14): with a cookie yt-dlp titles every reel "Video", so the row, the
|
||||
# file name and the share preview all said "Video". The post's text is in the description.
|
||||
u = _user(db, "fb-title@example.com")
|
||||
a, j = _asset_and_job(db, u, sig="shared", asset_status="downloading", job_status="running")
|
||||
monkeypatch.setattr(settings, "download_root", str(tmp_path))
|
||||
monkeypatch.setattr(pipeline.urlguard, "check_url", lambda url: None)
|
||||
|
||||
def extract(job_id, asset_id, url, spec, staging, adapter, **kwargs):
|
||||
media = staging / "2131884261040652.m4a"
|
||||
_ffmpeg_media(media, "-c:a", "aac")
|
||||
return {
|
||||
"id": "2131884261040652",
|
||||
"ext": "m4a",
|
||||
"title": "Video",
|
||||
"description": "Tune of The Day, Argy playing his version",
|
||||
"extractor_key": "Facebook",
|
||||
"filepath": str(media),
|
||||
}
|
||||
|
||||
monkeypatch.setattr(pipeline, "_extract_with_fallback", extract)
|
||||
|
||||
pipeline._download(j.id, a.id, "url", _REEL, {"mode": "a"}, u.id)
|
||||
|
||||
db.expire_all()
|
||||
asset, job = db.get(MediaAsset, a.id), db.get(DownloadJob, j.id)
|
||||
assert asset.title == job.display_name == "Tune of The Day, Argy playing his version"
|
||||
assert "Tune_of_The_Day" in asset.rel_path
|
||||
|
||||
|
||||
def test_the_early_title_takes_the_posts_text_too(db):
|
||||
u = _user(db, "fb-early-title@example.com")
|
||||
a, _ = _asset_and_job(db, u, sig="shared", asset_status="downloading", job_status="running")
|
||||
a.title = None
|
||||
db.commit()
|
||||
|
||||
pipeline._fill_asset_meta_early(
|
||||
a.id,
|
||||
{
|
||||
"id": "1",
|
||||
"title": "Video",
|
||||
"description": "Almás palacsinta",
|
||||
"extractor_key": "Facebook",
|
||||
},
|
||||
)
|
||||
|
||||
db.expire_all()
|
||||
assert db.get(MediaAsset, a.id).title == "Almás palacsinta"
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("ffmpeg") is None, reason="ffmpeg not available")
|
||||
def test_an_mp3_download_is_stored_as_an_mp3(db, monkeypatch, tmp_path):
|
||||
# Prod job 268: the "Audio (MP3)" profile's extracted .mp3 was remuxed into an .mp4 and served as
|
||||
# video/mp4, which iPad's WebKit will not play. It stays an .mp3 (served as audio/mpeg).
|
||||
u = _user(db, "fb-mp3@example.com")
|
||||
a, j = _asset_and_job(db, u, sig="shared", asset_status="downloading", job_status="running")
|
||||
monkeypatch.setattr(settings, "download_root", str(tmp_path))
|
||||
monkeypatch.setattr(pipeline.urlguard, "check_url", lambda url: None)
|
||||
|
||||
def extract(job_id, asset_id, url, spec, staging, adapter, **kwargs):
|
||||
media = staging / "2131884261040652.mp3"
|
||||
_ffmpeg_media(media, "-c:a", "libmp3lame")
|
||||
return {"id": "2131884261040652", "ext": "mp3", "title": "Reel", "filepath": str(media)}
|
||||
|
||||
monkeypatch.setattr(pipeline, "_extract_with_fallback", extract)
|
||||
|
||||
pipeline._download(
|
||||
j.id, a.id, "url", _REEL, formats.normalize({"mode": "a", "audio_format": "mp3"}), u.id
|
||||
)
|
||||
|
||||
db.expire_all()
|
||||
asset = db.get(MediaAsset, a.id)
|
||||
assert (asset.container, asset.acodec, asset.media_kind) == ("mp3", "mp3", "audio")
|
||||
assert asset.rel_path.endswith(".mp3")
|
||||
|
||||
Reference in New Issue
Block a user