mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-10-26 20:20:46 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			163 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import functools
 | |
| import json
 | |
| 
 | |
| from .common import InfoExtractor
 | |
| from ..utils import (
 | |
|     ExtractorError,
 | |
|     OnDemandPagedList,
 | |
|     determine_ext,
 | |
|     int_or_none,
 | |
|     try_get,
 | |
| )
 | |
| 
 | |
| 
 | |
| class MurrtubeIE(InfoExtractor):
 | |
|     _VALID_URL = r'''(?x)
 | |
|                         (?:
 | |
|                             murrtube:|
 | |
|                             https?://murrtube\.net/videos/(?P<slug>[a-z0-9\-]+)\-
 | |
|                         )
 | |
|                         (?P<id>[a-f0-9]{8}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{12})
 | |
|                     '''
 | |
|     _TEST = {
 | |
|         'url': 'https://murrtube.net/videos/inferno-x-skyler-148b6f2a-fdcc-4902-affe-9c0f41aaaca0',
 | |
|         'md5': '169f494812d9a90914b42978e73aa690',
 | |
|         'info_dict': {
 | |
|             'id': '148b6f2a-fdcc-4902-affe-9c0f41aaaca0',
 | |
|             'ext': 'mp4',
 | |
|             'title': 'Inferno X Skyler',
 | |
|             'description': 'Humping a very good slutty sheppy (roomate)',
 | |
|             'thumbnail': r're:^https?://.*\.jpg$',
 | |
|             'duration': 284,
 | |
|             'uploader': 'Inferno Wolf',
 | |
|             'age_limit': 18,
 | |
|             'comment_count': int,
 | |
|             'view_count': int,
 | |
|             'like_count': int,
 | |
|             'tags': ['hump', 'breed', 'Fursuit', 'murrsuit', 'bareback'],
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     def _download_gql(self, video_id, op, note=None, fatal=True):
 | |
|         result = self._download_json(
 | |
|             'https://murrtube.net/graphql',
 | |
|             video_id, note, data=json.dumps(op).encode(), fatal=fatal,
 | |
|             headers={'Content-Type': 'application/json'})
 | |
|         return result['data']
 | |
| 
 | |
|     def _real_extract(self, url):
 | |
|         video_id = self._match_id(url)
 | |
|         data = self._download_gql(video_id, {
 | |
|             'operationName': 'Medium',
 | |
|             'variables': {
 | |
|                 'id': video_id,
 | |
|             },
 | |
|             'query': '''\
 | |
| query Medium($id: ID!) {
 | |
|   medium(id: $id) {
 | |
|     title
 | |
|     description
 | |
|     key
 | |
|     duration
 | |
|     commentsCount
 | |
|     likesCount
 | |
|     viewsCount
 | |
|     thumbnailKey
 | |
|     tagList
 | |
|     user {
 | |
|       name
 | |
|       __typename
 | |
|     }
 | |
|     __typename
 | |
|   }
 | |
| }'''})
 | |
|         meta = data['medium']
 | |
| 
 | |
|         storage_url = 'https://storage.murrtube.net/murrtube/'
 | |
|         format_url = storage_url + meta.get('key', '')
 | |
|         thumbnail = storage_url + meta.get('thumbnailKey', '')
 | |
| 
 | |
|         if determine_ext(format_url) == 'm3u8':
 | |
|             formats = self._extract_m3u8_formats(
 | |
|                 format_url, video_id, 'mp4', entry_protocol='m3u8_native', fatal=False)
 | |
|         else:
 | |
|             formats = [{'url': format_url}]
 | |
| 
 | |
|         return {
 | |
|             'id': video_id,
 | |
|             'title': meta.get('title'),
 | |
|             'description': meta.get('description'),
 | |
|             'formats': formats,
 | |
|             'thumbnail': thumbnail,
 | |
|             'duration': int_or_none(meta.get('duration')),
 | |
|             'uploader': try_get(meta, lambda x: x['user']['name']),
 | |
|             'view_count': meta.get('viewsCount'),
 | |
|             'like_count': meta.get('likesCount'),
 | |
|             'comment_count': meta.get('commentsCount'),
 | |
|             'tags': meta.get('tagList'),
 | |
|             'age_limit': 18,
 | |
|         }
 | |
| 
 | |
| 
 | |
| class MurrtubeUserIE(MurrtubeIE):  # XXX: Do not subclass from concrete IE
 | |
|     IE_DESC = 'Murrtube user profile'
 | |
|     _VALID_URL = r'https?://murrtube\.net/(?P<id>[^/]+)$'
 | |
|     _TEST = {
 | |
|         'url': 'https://murrtube.net/stormy',
 | |
|         'info_dict': {
 | |
|             'id': 'stormy',
 | |
|         },
 | |
|         'playlist_mincount': 27,
 | |
|     }
 | |
|     _PAGE_SIZE = 10
 | |
| 
 | |
|     def _fetch_page(self, username, user_id, page):
 | |
|         data = self._download_gql(username, {
 | |
|             'operationName': 'Media',
 | |
|             'variables': {
 | |
|                 'limit': self._PAGE_SIZE,
 | |
|                 'offset': page * self._PAGE_SIZE,
 | |
|                 'sort': 'latest',
 | |
|                 'userId': user_id,
 | |
|             },
 | |
|             'query': '''\
 | |
| query Media($q: String, $sort: String, $userId: ID, $offset: Int!, $limit: Int!) {
 | |
|   media(q: $q, sort: $sort, userId: $userId, offset: $offset, limit: $limit) {
 | |
|     id
 | |
|     __typename
 | |
|   }
 | |
| }'''},
 | |
|             'Downloading page {0}'.format(page + 1))
 | |
|         if data is None:
 | |
|             raise ExtractorError(f'Failed to retrieve video list for page {page + 1}')
 | |
| 
 | |
|         media = data['media']
 | |
| 
 | |
|         for entry in media:
 | |
|             yield self.url_result('murrtube:{0}'.format(entry['id']), MurrtubeIE.ie_key())
 | |
| 
 | |
|     def _real_extract(self, url):
 | |
|         username = self._match_id(url)
 | |
|         data = self._download_gql(username, {
 | |
|             'operationName': 'User',
 | |
|             'variables': {
 | |
|                 'id': username,
 | |
|             },
 | |
|             'query': '''\
 | |
| query User($id: ID!) {
 | |
|   user(id: $id) {
 | |
|     id
 | |
|     __typename
 | |
|   }
 | |
| }'''},
 | |
|             'Downloading user info')
 | |
|         if data is None:
 | |
|             raise ExtractorError('Failed to fetch user info')
 | |
| 
 | |
|         user = data['user']
 | |
| 
 | |
|         entries = OnDemandPagedList(functools.partial(
 | |
|             self._fetch_page, username, user.get('id')), self._PAGE_SIZE)
 | |
| 
 | |
|         return self.playlist_result(entries, username)
 | 
