1
0
mirror of https://github.com/yt-dlp/yt-dlp.git synced 2025-07-18 03:08:31 +00:00

[ie/locipo] Add extractor

This commit is contained in:
gravesducking 2025-07-07 03:50:44 +09:00
parent eff0759705
commit 05c15e38ef
2 changed files with 175 additions and 0 deletions

View File

@ -1059,6 +1059,7 @@
)
from .livestreamfails import LivestreamfailsIE
from .lnk import LnkIE
from .locipo import LocipoIE
from .loco import LocoIE
from .loom import (
LoomFolderIE,

174
yt_dlp/extractor/locipo.py Normal file
View File

@ -0,0 +1,174 @@
from typing import Literal
from yt_dlp.utils._utils import classproperty
from .common import InfoExtractor
from ..utils import (
filter_dict,
int_or_none,
parse_iso8601,
str_or_none,
traverse_obj,
url_or_none,
)
class LocipoIE(InfoExtractor):
@classproperty
def IE_NAME(cls) -> Literal['locipo']:
return 'locipo'
IE_DESC = 'Locipo (ロキポ) Video/Playlist'
_VALID_URL = r'https?://locipo\.jp/creative/(?P<creative_id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(\?.*list=(?P<playlist_id>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))?'
_TESTS = [
{
'url': 'https://locipo.jp/creative/fb5ffeaa-398d-45ce-bb49-0e221b5f94f1',
'info_dict': {
'ext': 'mp4',
'id': 'fb5ffeaa-398d-45ce-bb49-0e221b5f94f1',
'series': 'リアルカレカノ',
'series_id': 'b865b972-99fe-41d5-a72c-8ed5c42132bd',
'duration': 3622,
'title': 'リアルカレカノ#4 ~伊達さゆりと勉強しよっ?~',
'description': 'TVアニメ「ラブライブスーパースター!!」澁谷かのん役などで\n活躍中の人気声優「伊達さゆり」さんと、恋人気分が味わえるコンテンツが登場!\n\n全てカレシ・カジョの1人称目線で撮影しているため\nこの動画でしか味わえない、ドキドキ感が満載!\n一緒に勉強したり…ご飯を食べたり…相談に乗ってもらったり…\nいろんなシチュエーションを楽しんでください!\n',
'uploader': 'thk',
'uploader_id': '1',
'thumbnail': 'https://dophkbxgy39ig.cloudfront.net/store/creatives/99190/large-51fec5367d73fc55dc364250885dfb2e.png',
'timestamp': 1711789200,
'modified_timestamp': 1725415481,
'upload_date': '20240330',
'modified_date': '20240904',
},
},
{
'url': 'https://locipo.jp/creative/8be557b9-5a97-4092-825e-5cb8c72b36ab?list=3058b313-3a7c-4d64-b067-d3d870b4b17d&noautoplay=&redirect=true',
'info_dict': {
'id': '3058b313-3a7c-4d64-b067-d3d870b4b17d',
'title': '達眼の戦士s ',
'description': '今注目のeスポーツで活躍するプロに密着!\n勝利への強いこだわりに迫るドキュメントバラエティ',
},
'playlist_count': 2,
},
{
'url': 'https://locipo.jp/creative/867176a9-cfd8-4807-b5f0-e41a549ba588?list=07738b35-6ce6-48b6-92f7-00167a95bb12',
'info_dict': {
'id': '07738b35-6ce6-48b6-92f7-00167a95bb12',
'title': 'チャント!特集',
},
'playlist_mincount': 30,
},
]
def _real_extract(self, url: str):
creative_id = self._match_valid_url(url).group('creative_id') # type: ignore
try:
playlist_id = self._match_valid_url(url).group('playlist_id') # type: ignore
except AttributeError:
playlist_id = ''
if playlist_id and self.get_param('noplaylist'):
self.to_screen(f'--no-playlist option specified. Processing only video {creative_id}')
playlist_id = ''
elif playlist_id:
self.to_screen(f'Processing playlist ID {playlist_id}. if you want to process only the video {creative_id}, use --no-playlist option')
if not playlist_id:
creative_data = self._download_json(
url_or_request=f'https://api.locipo.jp/api/v1/creatives/{creative_id}',
video_id=creative_id,
headers=filter_dict(
{
'accept': 'application/json, text/plain, */*',
'origin': 'https://locipo.jp',
'referer': 'https://locipo.jp/',
},
),
)
return {
# traverse_obj(creative_data, ('video', 'hls', {str})) is used to extract the HLS URL
'formats': self._extract_m3u8_formats(m3u8_url=traverse_obj(creative_data, ('video', 'hls', {str})), video_id=creative_id), # type: ignore
'id': creative_id,
**traverse_obj(
creative_data,
{
'series': ('playlist', 'title', {str}),
'series_id': ('playlist', 'id', {str}),
'duration': ('video', 'duration', {int_or_none}),
'title': ('title', {str}),
'description': ('description', {str}),
'uploader': ('station_cd', {str}),
'uploader_id': ('station_id', {str}),
'thumbnail': ('thumb', {url_or_none}),
'timestamp': ('broadcast_started_at', {parse_iso8601}),
'modified_timestamp': ('updated_at', {parse_iso8601}),
},
), # type: ignore
}
playlist_data = self._download_json(
url_or_request=f'https://api.locipo.jp/api/v1/playlists/{playlist_id}',
video_id=playlist_id,
headers=filter_dict(
{
'accept': 'application/json, text/plain, */*',
'origin': 'https://locipo.jp',
'referer': 'https://locipo.jp/',
},
),
)
# NOTE: This API can return up to 1000 videos. Since there doesn't seem to be any playlist with more than 1000 items at the moment, pagination is currently not implemented.
playlist_creatives_data = self._download_json(
url_or_request=f'https://api.locipo.jp/api/v1/playlists/{playlist_id}/creatives',
video_id=None,
headers=filter_dict(
{
'accept': 'application/json, text/plain, */*',
'origin': 'https://locipo.jp',
'referer': 'https://locipo.jp/',
},
),
)
entries = []
for creative in playlist_creatives_data.get('items', []): # type: ignore
entries.append(
{
**traverse_obj(
creative,
{
'id': ('id', {str}),
'duration': ('video', 'duration', {int_or_none}),
'title': ('title', {str}),
'description': ('description', {str_or_none}),
'uploader': ('station_cd', {str_or_none}),
'uploader_id': ('station_id', {str_or_none}),
'thumbnail': ('thumb', {url_or_none}),
'timestamp': ('broadcast_started_at', {parse_iso8601}),
'modified_timestamp': ('updated_at', {parse_iso8601}),
},
), # type: ignore
**traverse_obj(
playlist_data,
{
'series': ('title', {str}),
'series_id': ('id', {str}),
},
), # type: ignore
'formats': self._extract_m3u8_formats(
m3u8_url=traverse_obj(creative, ('video', 'hls', {str})), # type: ignore
video_id=traverse_obj(creative, ('id', {str})), # type: ignore
),
},
)
return self.playlist_result(
entries=entries,
playlist_id=playlist_id,
playlist_title=traverse_obj(playlist_data, ('title', {str})), # type: ignore
playlist_description=traverse_obj(playlist_data, ('description', {str_or_none})), # type: ignore
)