1
0
mirror of https://github.com/instaloader/instaloader.git synced 2024-07-07 11:42:38 +02:00

Download IGTV Videos of a Profile (--igtv)

This commit is contained in:
Alexander Graf 2020-03-14 16:56:51 +01:00
parent 41cf260fdd
commit 6cc2ed54ea
8 changed files with 89 additions and 15 deletions

View File

@ -57,7 +57,8 @@
::
instaloader [--comments] [--geotags] [--stories] [--highlights] [--tagged]
instaloader [--comments] [--geotags]
[--stories] [--highlights] [--tagged] [--igtv]
[--login YOUR-USERNAME] [--fast-update]
profile | "#hashtag" | :stories | :feed | :saved

View File

@ -77,10 +77,13 @@ Instaloader supports the following targets:
instructs Instaloader to also **download the user's stories**,
- :option:`--highlights`
to **download highlights of each profile that is downloaded**, and
to **download highlights of each profile that is downloaded**,
- :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"``
Posts with a certain **hashtag** (the quotes are usually necessary),

View File

@ -129,6 +129,12 @@ What to Download of each Profile
.. versionadded:: 4.1
.. option:: --igtv
Also download IGTV videos.
.. versionadded:: 4.3
.. option:: --stories-only
.. deprecated:: 4.1

View File

@ -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]
profile | "#hashtag" | %location_id |
:stories | :feed | :saved

View File

@ -19,7 +19,8 @@ def usage_string():
argv0 = os.path.basename(sys.argv[0])
argv0 = "instaloader" if argv0 == "__main__.py" else argv0
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}} profile | "#hashtag" | %%location_id | :stories | :feed | :saved
{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,
sessionfile: Optional[str] = None,
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,
max_count: Optional[int] = None, post_filter_str: Optional[str] = None,
storyitem_filter_str: Optional[str] = None) -> None:
@ -158,7 +162,8 @@ def _main(instaloader: Instaloader, targetlist: List[str],
try:
profile = instaloader.check_profile_id(target)
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."
.format(target))
else:
@ -169,7 +174,7 @@ def _main(instaloader: Instaloader, targetlist: List[str],
# 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.
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("Trying again anonymously, helps in case you are just blocked.")
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:
instaloader.context.error("Warning: Use --login to download HD version of profile pictures.")
instaloader.download_profiles(profiles,
download_profile_pic, download_posts, download_tagged, download_highlights,
download_stories, fast_update, post_filter, storyitem_filter)
download_profile_pic, download_posts, download_tagged, download_igtv,
download_highlights, download_stories,
fast_update, post_filter, storyitem_filter)
if anonymous_retry_profiles:
instaloader.context.log("Downloading anonymously: {}"
.format(' '.join([p.username for p in anonymous_retry_profiles])))
with instaloader.anonymous_copy() as anonymous_loader:
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)
except KeyboardInterrupt:
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.')
g_prof.add_argument('--tagged', action='store_true',
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")
@ -411,6 +419,7 @@ def main():
download_stories=download_stories,
download_highlights=args.highlights,
download_tagged=args.tagged,
download_igtv=args.igtv,
fast_update=args.fast_update,
max_count=int(args.count) if args.count is not None else None,
post_filter_str=args.post_filter,

View File

@ -917,6 +917,23 @@ class Instaloader:
if fast_update and not downloaded:
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:
if ((format_string_contains_key(self.dirname_pattern, 'profile') or
format_string_contains_key(self.dirname_pattern, 'target'))):
@ -989,7 +1006,10 @@ class Instaloader:
def download_profiles(self, profiles: Set[Profile],
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,
post_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 posts: not :option:`--no-posts`.
:param tagged: :option:`--tagged`.
:param igtv: :option:`--igtv`.
:param highlights: :option:`--highlights`.
:param stories: :option:`--stories`.
:param fast_update: :option:`--fast-update`.
@ -1009,7 +1030,11 @@ class Instaloader:
Whether :exc:`LoginRequiredException` and :exc:`PrivateProfileNotFollowedException` should be raised or
catched and printed with :meth:`InstaloaderContext.error_catcher`.
.. versionadded:: 4.1"""
.. versionadded:: 4.1
.. versionchanged:: 4.3
Add `igtv` parameter.
"""
@contextmanager
def _error_raiser(_str):
@ -1035,7 +1060,7 @@ class Instaloader:
self.save_metadata_json(json_filename, profile)
# 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:
raise LoginRequiredException("--login=USERNAME required.")
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)):
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
if highlights:
with self.context.error_catcher('Download highlights of {}'.format(profile_name)):

View File

@ -518,10 +518,11 @@ class Profile:
def _asdict(self):
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_owner_to_timeline_media', None)
json_node.pop('edge_saved_media', None)
json_node.pop('edge_felix_video_timeline', None)
if self._iphone_struct_:
json_node['iphone_struct'] = self._iphone_struct_
return json_node
@ -598,6 +599,10 @@ class Profile:
def mediacount(self) -> int:
return self._metadata('edge_owner_to_timeline_media', 'count')
@property
def igtvcount(self) -> int:
return self._metadata('edge_felix_video_timeline', 'count')
@property
def followers(self) -> int:
return self._metadata('edge_followed_by', 'count')
@ -741,6 +746,19 @@ class Profile:
lambda d: d['data']['user']['edge_user_to_photos_of_you'],
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']:
"""
Retrieve list of followers of given profile.

View File

@ -12,6 +12,7 @@ import instaloader
PROFILE_WITH_HIGHLIGHTS = 325732271
PUBLIC_PROFILE = "selenagomez"
PUBLIC_PROFILE_ID = 460563723
PUBLIC_PROFILE_WITH_IGTV = "natgeo"
HASHTAG = "kitten"
LOCATION = "362629379"
OWN_USERNAME = "aandergr"
@ -104,6 +105,11 @@ class TestInstaloaderAnonymously(unittest.TestCase):
PAGING_MAX_COUNT):
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):