mirror of
https://github.com/instaloader/instaloader.git
synced 2024-08-18 12:49:38 +02:00
Download IGTV Videos of a Profile (--igtv)
This commit is contained in:
parent
41cf260fdd
commit
6cc2ed54ea
@ -57,7 +57,8 @@
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
instaloader [--comments] [--geotags] [--stories] [--highlights] [--tagged]
|
instaloader [--comments] [--geotags]
|
||||||
|
[--stories] [--highlights] [--tagged] [--igtv]
|
||||||
[--login YOUR-USERNAME] [--fast-update]
|
[--login YOUR-USERNAME] [--fast-update]
|
||||||
profile | "#hashtag" | :stories | :feed | :saved
|
profile | "#hashtag" | :stories | :feed | :saved
|
||||||
|
|
||||||
|
@ -77,10 +77,13 @@ Instaloader supports the following targets:
|
|||||||
instructs Instaloader to also **download the user's stories**,
|
instructs Instaloader to also **download the user's stories**,
|
||||||
|
|
||||||
- :option:`--highlights`
|
- :option:`--highlights`
|
||||||
to **download highlights of each profile that is downloaded**, and
|
to **download highlights of each profile that is downloaded**,
|
||||||
|
|
||||||
- :option:`--tagged`
|
- :option:`--tagged`
|
||||||
to **download posts where the user is tagged**.
|
to **download posts where the user is tagged**, and
|
||||||
|
|
||||||
|
- :option:`--igtv`
|
||||||
|
to **download IGTV videos**.
|
||||||
|
|
||||||
- ``"#hashtag"``
|
- ``"#hashtag"``
|
||||||
Posts with a certain **hashtag** (the quotes are usually necessary),
|
Posts with a certain **hashtag** (the quotes are usually necessary),
|
||||||
|
@ -129,6 +129,12 @@ What to Download of each Profile
|
|||||||
|
|
||||||
.. versionadded:: 4.1
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
.. option:: --igtv
|
||||||
|
|
||||||
|
Also download IGTV videos.
|
||||||
|
|
||||||
|
.. versionadded:: 4.3
|
||||||
|
|
||||||
.. option:: --stories-only
|
.. option:: --stories-only
|
||||||
|
|
||||||
.. deprecated:: 4.1
|
.. deprecated:: 4.1
|
||||||
|
@ -45,7 +45,8 @@ See :ref:`install` for more options on how to install Instaloader.
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
instaloader [--comments] [--geotags] [--stories] [--highlights] [--tagged]
|
instaloader [--comments] [--geotags]
|
||||||
|
[--stories] [--highlights] [--tagged] [--igtv]
|
||||||
[--login YOUR-USERNAME] [--fast-update]
|
[--login YOUR-USERNAME] [--fast-update]
|
||||||
profile | "#hashtag" | %location_id |
|
profile | "#hashtag" | %location_id |
|
||||||
:stories | :feed | :saved
|
:stories | :feed | :saved
|
||||||
|
@ -19,7 +19,8 @@ def usage_string():
|
|||||||
argv0 = os.path.basename(sys.argv[0])
|
argv0 = os.path.basename(sys.argv[0])
|
||||||
argv0 = "instaloader" if argv0 == "__main__.py" else argv0
|
argv0 = "instaloader" if argv0 == "__main__.py" else argv0
|
||||||
return """
|
return """
|
||||||
{0} [--comments] [--geotags] [--stories] [--highlights] [--tagged]
|
{0} [--comments] [--geotags]
|
||||||
|
{2:{1}} [--stories] [--highlights] [--tagged] [--igtv]
|
||||||
{2:{1}} [--login YOUR-USERNAME] [--fast-update]
|
{2:{1}} [--login YOUR-USERNAME] [--fast-update]
|
||||||
{2:{1}} profile | "#hashtag" | %%location_id | :stories | :feed | :saved
|
{2:{1}} profile | "#hashtag" | %%location_id | :stories | :feed | :saved
|
||||||
{0} --help""".format(argv0, len(argv0), '')
|
{0} --help""".format(argv0, len(argv0), '')
|
||||||
@ -61,7 +62,10 @@ def _main(instaloader: Instaloader, targetlist: List[str],
|
|||||||
username: Optional[str] = None, password: Optional[str] = None,
|
username: Optional[str] = None, password: Optional[str] = None,
|
||||||
sessionfile: Optional[str] = None,
|
sessionfile: Optional[str] = None,
|
||||||
download_profile_pic: bool = True, download_posts=True,
|
download_profile_pic: bool = True, download_posts=True,
|
||||||
download_stories: bool = False, download_highlights: bool = False, download_tagged: bool = False,
|
download_stories: bool = False,
|
||||||
|
download_highlights: bool = False,
|
||||||
|
download_tagged: bool = False,
|
||||||
|
download_igtv: bool = False,
|
||||||
fast_update: bool = False,
|
fast_update: bool = False,
|
||||||
max_count: Optional[int] = None, post_filter_str: Optional[str] = None,
|
max_count: Optional[int] = None, post_filter_str: Optional[str] = None,
|
||||||
storyitem_filter_str: Optional[str] = None) -> None:
|
storyitem_filter_str: Optional[str] = None) -> None:
|
||||||
@ -158,7 +162,8 @@ def _main(instaloader: Instaloader, targetlist: List[str],
|
|||||||
try:
|
try:
|
||||||
profile = instaloader.check_profile_id(target)
|
profile = instaloader.check_profile_id(target)
|
||||||
if instaloader.context.is_logged_in and profile.has_blocked_viewer:
|
if instaloader.context.is_logged_in and profile.has_blocked_viewer:
|
||||||
if download_profile_pic or ((download_posts or download_tagged) and not profile.is_private):
|
if download_profile_pic or ((download_posts or download_tagged or download_igtv)
|
||||||
|
and not profile.is_private):
|
||||||
raise ProfileNotExistsException("{} blocked you; But we download her anonymously."
|
raise ProfileNotExistsException("{} blocked you; But we download her anonymously."
|
||||||
.format(target))
|
.format(target))
|
||||||
else:
|
else:
|
||||||
@ -169,7 +174,7 @@ def _main(instaloader: Instaloader, targetlist: List[str],
|
|||||||
# Not only our profile.has_blocked_viewer condition raises ProfileNotExistsException,
|
# Not only our profile.has_blocked_viewer condition raises ProfileNotExistsException,
|
||||||
# check_profile_id() also does, since access to blocked profile may be responded with 404.
|
# check_profile_id() also does, since access to blocked profile may be responded with 404.
|
||||||
if instaloader.context.is_logged_in and (download_profile_pic or download_posts or
|
if instaloader.context.is_logged_in and (download_profile_pic or download_posts or
|
||||||
download_tagged):
|
download_tagged or download_igtv):
|
||||||
instaloader.context.log(err)
|
instaloader.context.log(err)
|
||||||
instaloader.context.log("Trying again anonymously, helps in case you are just blocked.")
|
instaloader.context.log("Trying again anonymously, helps in case you are just blocked.")
|
||||||
with instaloader.anonymous_copy() as anonymous_loader:
|
with instaloader.anonymous_copy() as anonymous_loader:
|
||||||
@ -185,14 +190,15 @@ def _main(instaloader: Instaloader, targetlist: List[str],
|
|||||||
if profiles and download_profile_pic and not instaloader.context.is_logged_in:
|
if profiles and download_profile_pic and not instaloader.context.is_logged_in:
|
||||||
instaloader.context.error("Warning: Use --login to download HD version of profile pictures.")
|
instaloader.context.error("Warning: Use --login to download HD version of profile pictures.")
|
||||||
instaloader.download_profiles(profiles,
|
instaloader.download_profiles(profiles,
|
||||||
download_profile_pic, download_posts, download_tagged, download_highlights,
|
download_profile_pic, download_posts, download_tagged, download_igtv,
|
||||||
download_stories, fast_update, post_filter, storyitem_filter)
|
download_highlights, download_stories,
|
||||||
|
fast_update, post_filter, storyitem_filter)
|
||||||
if anonymous_retry_profiles:
|
if anonymous_retry_profiles:
|
||||||
instaloader.context.log("Downloading anonymously: {}"
|
instaloader.context.log("Downloading anonymously: {}"
|
||||||
.format(' '.join([p.username for p in anonymous_retry_profiles])))
|
.format(' '.join([p.username for p in anonymous_retry_profiles])))
|
||||||
with instaloader.anonymous_copy() as anonymous_loader:
|
with instaloader.anonymous_copy() as anonymous_loader:
|
||||||
anonymous_loader.download_profiles(anonymous_retry_profiles,
|
anonymous_loader.download_profiles(anonymous_retry_profiles,
|
||||||
download_profile_pic, download_posts, download_tagged,
|
download_profile_pic, download_posts, download_tagged, download_igtv,
|
||||||
fast_update=fast_update, post_filter=post_filter)
|
fast_update=fast_update, post_filter=post_filter)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nInterrupted by user.", file=sys.stderr)
|
print("\nInterrupted by user.", file=sys.stderr)
|
||||||
@ -287,6 +293,8 @@ def main():
|
|||||||
help='Also download highlights of each profile that is downloaded. Requires --login.')
|
help='Also download highlights of each profile that is downloaded. Requires --login.')
|
||||||
g_prof.add_argument('--tagged', action='store_true',
|
g_prof.add_argument('--tagged', action='store_true',
|
||||||
help='Also download posts where each profile is tagged.')
|
help='Also download posts where each profile is tagged.')
|
||||||
|
g_prof.add_argument('--igtv', action='store_true',
|
||||||
|
help='Also download IGTV videos.')
|
||||||
|
|
||||||
g_cond = parser.add_argument_group("Which Posts to Download")
|
g_cond = parser.add_argument_group("Which Posts to Download")
|
||||||
|
|
||||||
@ -411,6 +419,7 @@ def main():
|
|||||||
download_stories=download_stories,
|
download_stories=download_stories,
|
||||||
download_highlights=args.highlights,
|
download_highlights=args.highlights,
|
||||||
download_tagged=args.tagged,
|
download_tagged=args.tagged,
|
||||||
|
download_igtv=args.igtv,
|
||||||
fast_update=args.fast_update,
|
fast_update=args.fast_update,
|
||||||
max_count=int(args.count) if args.count is not None else None,
|
max_count=int(args.count) if args.count is not None else None,
|
||||||
post_filter_str=args.post_filter,
|
post_filter_str=args.post_filter,
|
||||||
|
@ -917,6 +917,23 @@ class Instaloader:
|
|||||||
if fast_update and not downloaded:
|
if fast_update and not downloaded:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def download_igtv(self, profile: Profile, fast_update: bool = False,
|
||||||
|
post_filter: Optional[Callable[[Post], bool]] = None) -> None:
|
||||||
|
"""Download IGTV videos of a profile.
|
||||||
|
|
||||||
|
.. versionadded:: 4.3"""
|
||||||
|
self.context.log("Retrieving IGTV videos for profile {}.".format(profile.username))
|
||||||
|
for number, post in enumerate(profile.get_igtv_posts()):
|
||||||
|
self.context.log("[{0:{w}d}/{1:{w}d}] ".format(number, profile.igtvcount, w=len(str(profile.igtvcount))),
|
||||||
|
end="", flush=True)
|
||||||
|
if post_filter is not None and not post_filter(post):
|
||||||
|
self.context.log('<{} skipped>'.format(post))
|
||||||
|
continue
|
||||||
|
with self.context.error_catcher('Download IGTV {}'.format(post.shortcode)):
|
||||||
|
downloaded = self.download_post(post, target=profile.username)
|
||||||
|
if fast_update and not downloaded:
|
||||||
|
break
|
||||||
|
|
||||||
def _get_id_filename(self, profile_name: str) -> str:
|
def _get_id_filename(self, profile_name: str) -> str:
|
||||||
if ((format_string_contains_key(self.dirname_pattern, 'profile') or
|
if ((format_string_contains_key(self.dirname_pattern, 'profile') or
|
||||||
format_string_contains_key(self.dirname_pattern, 'target'))):
|
format_string_contains_key(self.dirname_pattern, 'target'))):
|
||||||
@ -989,7 +1006,10 @@ class Instaloader:
|
|||||||
|
|
||||||
def download_profiles(self, profiles: Set[Profile],
|
def download_profiles(self, profiles: Set[Profile],
|
||||||
profile_pic: bool = True, posts: bool = True,
|
profile_pic: bool = True, posts: bool = True,
|
||||||
tagged: bool = False, highlights: bool = False, stories: bool = False,
|
tagged: bool = False,
|
||||||
|
igtv: bool = False,
|
||||||
|
highlights: bool = False,
|
||||||
|
stories: bool = False,
|
||||||
fast_update: bool = False,
|
fast_update: bool = False,
|
||||||
post_filter: Optional[Callable[[Post], bool]] = None,
|
post_filter: Optional[Callable[[Post], bool]] = None,
|
||||||
storyitem_filter: Optional[Callable[[Post], bool]] = None,
|
storyitem_filter: Optional[Callable[[Post], bool]] = None,
|
||||||
@ -1000,6 +1020,7 @@ class Instaloader:
|
|||||||
:param profile_pic: not :option:`--no-profile-pic`.
|
:param profile_pic: not :option:`--no-profile-pic`.
|
||||||
:param posts: not :option:`--no-posts`.
|
:param posts: not :option:`--no-posts`.
|
||||||
:param tagged: :option:`--tagged`.
|
:param tagged: :option:`--tagged`.
|
||||||
|
:param igtv: :option:`--igtv`.
|
||||||
:param highlights: :option:`--highlights`.
|
:param highlights: :option:`--highlights`.
|
||||||
:param stories: :option:`--stories`.
|
:param stories: :option:`--stories`.
|
||||||
:param fast_update: :option:`--fast-update`.
|
:param fast_update: :option:`--fast-update`.
|
||||||
@ -1009,7 +1030,11 @@ class Instaloader:
|
|||||||
Whether :exc:`LoginRequiredException` and :exc:`PrivateProfileNotFollowedException` should be raised or
|
Whether :exc:`LoginRequiredException` and :exc:`PrivateProfileNotFollowedException` should be raised or
|
||||||
catched and printed with :meth:`InstaloaderContext.error_catcher`.
|
catched and printed with :meth:`InstaloaderContext.error_catcher`.
|
||||||
|
|
||||||
.. versionadded:: 4.1"""
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
.. versionchanged:: 4.3
|
||||||
|
Add `igtv` parameter.
|
||||||
|
"""
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _error_raiser(_str):
|
def _error_raiser(_str):
|
||||||
@ -1035,7 +1060,7 @@ class Instaloader:
|
|||||||
self.save_metadata_json(json_filename, profile)
|
self.save_metadata_json(json_filename, profile)
|
||||||
|
|
||||||
# Catch some errors
|
# Catch some errors
|
||||||
if profile.is_private and (tagged or highlights or posts):
|
if profile.is_private and (tagged or igtv or highlights or posts):
|
||||||
if not self.context.is_logged_in:
|
if not self.context.is_logged_in:
|
||||||
raise LoginRequiredException("--login=USERNAME required.")
|
raise LoginRequiredException("--login=USERNAME required.")
|
||||||
if not profile.followed_by_viewer and self.context.username != profile.username:
|
if not profile.followed_by_viewer and self.context.username != profile.username:
|
||||||
@ -1046,6 +1071,11 @@ class Instaloader:
|
|||||||
with self.context.error_catcher('Download tagged of {}'.format(profile_name)):
|
with self.context.error_catcher('Download tagged of {}'.format(profile_name)):
|
||||||
self.download_tagged(profile, fast_update=fast_update, post_filter=post_filter)
|
self.download_tagged(profile, fast_update=fast_update, post_filter=post_filter)
|
||||||
|
|
||||||
|
# Download IGTV, if requested
|
||||||
|
if igtv:
|
||||||
|
with self.context.error_catcher('Download IGTV of {}'.format(profile_name)):
|
||||||
|
self.download_igtv(profile, fast_update=fast_update, post_filter=post_filter)
|
||||||
|
|
||||||
# Download highlights, if requested
|
# Download highlights, if requested
|
||||||
if highlights:
|
if highlights:
|
||||||
with self.context.error_catcher('Download highlights of {}'.format(profile_name)):
|
with self.context.error_catcher('Download highlights of {}'.format(profile_name)):
|
||||||
|
@ -518,10 +518,11 @@ class Profile:
|
|||||||
|
|
||||||
def _asdict(self):
|
def _asdict(self):
|
||||||
json_node = self._node.copy()
|
json_node = self._node.copy()
|
||||||
# remove posts
|
# remove posts to avoid "Circular reference detected" exception
|
||||||
json_node.pop('edge_media_collections', None)
|
json_node.pop('edge_media_collections', None)
|
||||||
json_node.pop('edge_owner_to_timeline_media', None)
|
json_node.pop('edge_owner_to_timeline_media', None)
|
||||||
json_node.pop('edge_saved_media', None)
|
json_node.pop('edge_saved_media', None)
|
||||||
|
json_node.pop('edge_felix_video_timeline', None)
|
||||||
if self._iphone_struct_:
|
if self._iphone_struct_:
|
||||||
json_node['iphone_struct'] = self._iphone_struct_
|
json_node['iphone_struct'] = self._iphone_struct_
|
||||||
return json_node
|
return json_node
|
||||||
@ -598,6 +599,10 @@ class Profile:
|
|||||||
def mediacount(self) -> int:
|
def mediacount(self) -> int:
|
||||||
return self._metadata('edge_owner_to_timeline_media', 'count')
|
return self._metadata('edge_owner_to_timeline_media', 'count')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def igtvcount(self) -> int:
|
||||||
|
return self._metadata('edge_felix_video_timeline', 'count')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def followers(self) -> int:
|
def followers(self) -> int:
|
||||||
return self._metadata('edge_followed_by', 'count')
|
return self._metadata('edge_followed_by', 'count')
|
||||||
@ -741,6 +746,19 @@ class Profile:
|
|||||||
lambda d: d['data']['user']['edge_user_to_photos_of_you'],
|
lambda d: d['data']['user']['edge_user_to_photos_of_you'],
|
||||||
self._rhx_gis))
|
self._rhx_gis))
|
||||||
|
|
||||||
|
def get_igtv_posts(self) -> Iterator[Post]:
|
||||||
|
"""Retrieve all IGTV posts.
|
||||||
|
|
||||||
|
.. versionadded:: 4.3"""
|
||||||
|
self._obtain_metadata()
|
||||||
|
yield from (Post(self._context, node, self) for node in
|
||||||
|
self._context.graphql_node_list('bc78b344a68ed16dd5d7f264681c4c76',
|
||||||
|
{'id': self.userid},
|
||||||
|
'https://www.instagram.com/{0}/channel/'.format(self.username),
|
||||||
|
lambda d: d['data']['user']['edge_felix_video_timeline'],
|
||||||
|
self._rhx_gis,
|
||||||
|
self._metadata('edge_felix_video_timeline')))
|
||||||
|
|
||||||
def get_followers(self) -> Iterator['Profile']:
|
def get_followers(self) -> Iterator['Profile']:
|
||||||
"""
|
"""
|
||||||
Retrieve list of followers of given profile.
|
Retrieve list of followers of given profile.
|
||||||
|
@ -12,6 +12,7 @@ import instaloader
|
|||||||
PROFILE_WITH_HIGHLIGHTS = 325732271
|
PROFILE_WITH_HIGHLIGHTS = 325732271
|
||||||
PUBLIC_PROFILE = "selenagomez"
|
PUBLIC_PROFILE = "selenagomez"
|
||||||
PUBLIC_PROFILE_ID = 460563723
|
PUBLIC_PROFILE_ID = 460563723
|
||||||
|
PUBLIC_PROFILE_WITH_IGTV = "natgeo"
|
||||||
HASHTAG = "kitten"
|
HASHTAG = "kitten"
|
||||||
LOCATION = "362629379"
|
LOCATION = "362629379"
|
||||||
OWN_USERNAME = "aandergr"
|
OWN_USERNAME = "aandergr"
|
||||||
@ -104,6 +105,11 @@ class TestInstaloaderAnonymously(unittest.TestCase):
|
|||||||
PAGING_MAX_COUNT):
|
PAGING_MAX_COUNT):
|
||||||
print(post)
|
print(post)
|
||||||
|
|
||||||
|
def test_public_profile_igtv(self):
|
||||||
|
for post in islice(instaloader.Profile.from_username(self.L.context, PUBLIC_PROFILE_WITH_IGTV).get_igtv_posts(),
|
||||||
|
PAGING_MAX_COUNT):
|
||||||
|
print(post)
|
||||||
|
|
||||||
|
|
||||||
class TestInstaloaderLoggedIn(TestInstaloaderAnonymously):
|
class TestInstaloaderLoggedIn(TestInstaloaderAnonymously):
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user