@ -18,61 +18,120 @@ class MurrtubeIE(InfoExtractor):
_TESTS = [
{
" url " : " https://murrtube.net/videos/inferno-x-skyler-148b6f2a-fdcc-4902-affe-9c0f41aaaca0 " ,
" md5 " : " 70380878a77e8565d4aea7f68b8bbb35 " ,
" info_dict " : {
" id " : " ca885d8456b95de529b6723b158032e11115d " ,
" ext " : " mp4 " ,
" title " : " Inferno X Skyler " ,
" description " : " Humping a very good slutty sheppy (roomate) " ,
" uploader " : " Inferno Wolf " ,
" age_limit " : 18 ,
" thumbnail " : " https://storage.murrtube.net/murrtube-production/ekbs3zcfvuynnqfx72nn2tkokvsd "
' url ' : ' https://murrtube.net/videos/inferno-x-skyler-148b6f2a-fdcc-4902-affe-9c0f41aaaca0 ' ,
' md5 ' : ' 70380878a77e8565d4aea7f68b8bbb35 ' ,
' info_dict ' : {
' id ' : ' ca885d8456b95de529b6723b158032e11115d ' ,
' ext ' : ' mp4 ' ,
' title ' : ' Inferno X Skyler ' ,
' description ' : ' Humping a very good slutty sheppy (roomate) ' ,
' uploader ' : ' Inferno Wolf ' ,
' age_limit ' : 18 ,
' thumbnail ' : ' https://storage.murrtube.net/murrtube-production/ekbs3zcfvuynnqfx72nn2tkokvsd '
} ,
} ,
{
" url " : " https://murrtube.net/v/0J2Q " ,
" md5 " : " 31262f6ac56f0ca75e5a54a0f3fefcb6 " ,
" info_dict " : {
" id " : " 8442998c52134968d9caa36e473e1a6bac6ca " ,
" ext " : " mp4 " ,
" uploader " : " Hayel " ,
" title " : " Who ' s in charge now? " ,
" description " : """ Fenny sneaked into my bed room and played naughty with one of my plushies. I caught him in the act and wanted to punish him. He thought he was in charge and wanted to use me instead but he wasn ' t prepared on my butt milking him within just a minute. Fenny: @fenny_ad (both here and on Twitter) Hayel on Twitter: https://twitter.com/plushmods """ ,
" age_limit " : 18 ,
" thumbnail " : " https://storage.murrtube.net/murrtube-production/fb1ojjwiucufp34ya6hxu5vfqi5s "
' url ' : ' https://murrtube.net/v/0J2Q ' ,
' md5 ' : ' 31262f6ac56f0ca75e5a54a0f3fefcb6 ' ,
' info_dict ' : {
' id ' : ' 8442998c52134968d9caa36e473e1a6bac6ca ' ,
' ext ' : ' mp4 ' ,
' uploader ' : ' Hayel ' ,
' title ' : ' Who \' s in charge now? ' ,
' description ' : ' md5:795791e97e5b0f1805ea84573f02a997 ' ,
' age_limit ' : 18 ,
' thumbnail ' : ' https://storage.murrtube.net/murrtube-production/fb1ojjwiucufp34ya6hxu5vfqi5s '
}
}
]
def _real_extract ( self , url ) :
video_id = self . _match_valid_url ( url )
# TODO: This part could be smarter (Set and store age cookie?)
video_page = self . _download_webpage (
def _real_initialize ( self ) :
homepage = self . _download_webpage (
' https://murrtube.net ' , None , note = ' Getting session token ' )
data = self . _hidden_inputs ( video_ page)
data = self . _hidden_inputs ( homepage )
self . _download_webpage (
' https://murrtube.net/accept_age_check ' , None , ' Set age cookie ' , data = urlencode_postdata ( data ) )
def _real_extract ( self , url ) :
video_id = self . _match_valid_url ( url )
video_page = self . _download_webpage ( url , video_id )
video_attrs = extract_attributes ( self . _search_regex ( r ' (<video[^>]+>) ' , video_page , ' video ' ) )
playlist = video_attrs [ ' data-url ' ] . split ( ' ? ' ) [ 0 ]
matches = re . compile ( r ' https://storage.murrtube.net/murrtube-production/.+/(?P<id>.+)/index.m3u8 ' ) . match ( playlist ) . groupdict ( )
video_id = matches [ ' id ' ]
video_id = self . _search_regex ( r ' https://storage.murrtube.net/murrtube-production/.+/(?P<id>.+)/index.m3u8 ' , playlist , ' id ' , default = None )
formats = self . _extract_m3u8_formats ( playlist , video_id , ' mp4 ' , entry_protocol = ' m3u8_native ' , fatal = False )
title = self . _html_search_meta (
' og:title ' , video_page , display_name = ' title ' , fatal = True ) [ : - 11 ]
description = self . _html_search_meta (
' og:description ' , video_page , display_name = ' description ' , fatal = True )
thumbnail = self . _html_search_meta (
' og:image ' , video_page , display_name = ' thumbnail ' , fatal = True ) . split ( " ? " ) [ 0 ]
uploader = self . _html_search_regex (
r ' <span class= " pl-1 is-size-6 has-text-lighter " >(.+?)</span> ' , video_page , ' uploader ' , default = None )
return {
' id ' : video_id ,
' title ' : title,
' title ' : self . _og_search_title ( video_page ) ,
' age_limit ' : 18 ,
' formats ' : formats ,
' description ' : description ,
' thumbnail ' : thumbnail ,
' uploader ' : uploader ,
' description ' : self . _og_search_description ( video_page ) ,
' thumbnail ' : self . _og_search_thumbnail ( video_page ) ,
' uploader ' : self . _html_search_regex (
r ' <span class= " pl-1 is-size-6 has-text-lighter " >(.+?)</span> ' , video_page , ' uploader ' , default = None ) ,
' view_count ' : self . _search_regex ( r ' (?P<views>[ \ d,]+) <span class= " has-text-white " >Views< \ /span> ' , video_page , ' views ' , default = None ) ,
' like_count ' : self . _search_regex ( r ' (?P<likes>[ \ d,]+) <span class= " has-text-white " >Likes< \ /span> ' , video_page , ' likes ' , default = None ) ,
' comment_count ' : self . _search_regex ( r ' (?P<comment>[ \ d,]+) <span class= " has-text-white " >Comment< \ /span> ' , video_page , ' comment ' , default = None )
}
class MurrtubeUserIE ( MurrtubeIE ) : # XXX: Do not subclass from concrete IE
_WORKING = False
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 )