{"action":"created","comment":{"url":"https://api.github.com/repos/rg3/youtube-dl/pulls/comments/33889725","id":33889725,"diff_hunk":"@@ -887,85 +889,244 @@ def _apply_format_filter(self, format_spec, available_formats):\n                 '=': operator.eq,\n                 '!=': operator.ne,\n             }\n-            str_operator_rex = re.compile(r'''(?x)\\s*\\[\n+            str_operator_rex = re.compile(r'''(?x)\n                 \\s*(?P<key>ext|acodec|vcodec|container|protocol)\n                 \\s*(?P<op>%s)(?P<none_inclusive>\\s*\\?)?\n                 \\s*(?P<value>[a-zA-Z0-9_-]+)\n-                \\s*\\]$\n+                \\s*$\n                 ''' % '|'.join(map(re.escape, STR_OPERATORS.keys())))\n-            m = str_operator_rex.search(format_spec)\n+            m = str_operator_rex.search(filter_spec)\n             if m:\n                 comparison_value = m.group('value')\n                 op = STR_OPERATORS[m.group('op')]\n \n         if not m:\n-            raise ValueError('Invalid format specification %r' % format_spec)\n+            raise ValueError('Invalid filter specification %r' % filter_spec)\n \n         def _filter(f):\n             actual_value = f.get(m.group('key'))\n             if actual_value is None:\n                 return m.group('none_inclusive')\n             return op(actual_value, comparison_value)\n-        new_formats = [f for f in available_formats if _filter(f)]\n+        return _filter\n+\n+    def build_format_selector(self, format_spec):\n+        def syntax_error(note, start):\n+            message = (\n+                'Invalid format specification: '\n+                '{0}\\n\\t{1}\\n\\t{2}^'.format(note, format_spec, ' ' * start[1]))\n+            return SyntaxError(message)\n+\n+        PICKFIRST = 'PICKFIRST'\n+        MERGE = 'MERGE'\n+        SINGLE = 'SINGLE'\n+        GROUP = 'GROUP'\n+        FormatSelector = collections.namedtuple('FormatSelector', ['type', 'selector', 'filters'])\n+\n+        def _parse_filter(tokens):\n+            filter_parts = []\n+            for type, string, start, _, _ in tokens:\n+                if type == tokenize.OP and string == ']':\n+                    return ''.join(filter_parts)\n+                else:\n+                    filter_parts.append(string)\n+\n+        def _parse_format_selection(tokens, inside_merge=False, inside_choice=False, inside_group=False):\n+            selectors = []\n+            current_selector = None\n+            for type, string, start, _, _ in tokens:\n+                # ENCODING is only defined in python 3.x\n+                if type == getattr(tokenize, 'ENCODING', None):\n+                    continue\n+                elif type in [tokenize.NAME, tokenize.NUMBER]:\n+                    current_selector = FormatSelector(SINGLE, string, [])\n+                elif type == tokenize.OP:\n+                    if string == ')':\n+                        if not inside_group:\n+                            # ')' will be handled by the parentheses group\n+                            tokens.restore_last_token()\n+                        break\n+                    elif inside_merge and string in ['/', ',']:\n+                        tokens.restore_last_token()\n+                        break\n+                    elif inside_choice and string == ',':\n+                        tokens.restore_last_token()\n+                        break\n+                    elif string == ',':\n+                        selectors.append(current_selector)\n+                        current_selector = None\n+                    elif string == '/':\n+                        first_choice = current_selector\n+                        second_choice = _parse_format_selection(tokens, inside_choice=True)\n+                        current_selector = None\n+                        selectors.append(FormatSelector(PICKFIRST, (first_choice, second_choice), []))\n+                    elif string == '[':\n+                        if not current_selector:\n+                            current_selector = FormatSelector(SINGLE, 'best', [])\n+                        format_filter = _parse_filter(tokens)\n+                        current_selector.filters.append(format_filter)\n+                    elif string == '(':\n+                        if current_selector:\n+                            raise syntax_error('Unexpected \"(\"', start)\n+                        group = _parse_format_selection(tokens, inside_group=True)\n+                        current_selector = FormatSelector(GROUP, group, [])\n+                    elif string == '+':\n+                        video_selector = current_selector\n+                        audio_selector = _parse_format_selection(tokens, inside_merge=True)\n+                        current_selector = FormatSelector(MERGE, (video_selector, audio_selector), [])\n+                    else:\n+                        raise syntax_error('Operator not recognized: \"{0}\"'.format(string), start)\n+                elif type == tokenize.ENDMARKER:\n+                    break\n+            if current_selector:\n+                selectors.append(current_selector)\n+            return selectors\n+\n+        def _build_selector_function(selector):\n+            if isinstance(selector, list):\n+                fs = [_build_selector_function(s) for s in selector]\n+\n+                def selector_function(formats):\n+                    for f in fs:\n+                        for format in f(formats):\n+                            yield format\n+                return selector_function\n+            elif selector.type == GROUP:\n+                selector_function = _build_selector_function(selector.selector)\n+            elif selector.type == PICKFIRST:\n+                fs = [_build_selector_function(s) for s in selector.selector]\n+\n+                def selector_function(formats):\n+                    for f in fs:\n+                        picked_formats = list(f(formats))\n+                        if picked_formats:\n+                            return picked_formats\n+                    return []\n+            elif selector.type == SINGLE:\n+                format_spec = selector.selector\n+\n+                def selector_function(formats):\n+                    if format_spec == 'all':\n+                        for f in formats:\n+                            yield f\n+                    elif format_spec in ['best', 'worst', None]:\n+                        format_idx = 0 if format_spec == 'worst' else -1\n+                        audiovideo_formats = [\n+                            f for f in formats\n+                            if f.get('vcodec') != 'none' and f.get('acodec') != 'none']\n+                        if audiovideo_formats:\n+                            yield audiovideo_formats[format_idx]\n+                        # for audio only (soundcloud) or video only (imgur) urls, select the best/worst audio format\n+                        elif (all(f.get('acodec') != 'none' for f in formats) or\n+                              all(f.get('vcodec') != 'none' for f in formats)):\n+                            yield formats[format_idx]\n+                    elif format_spec == 'bestaudio':\n+                        audio_formats = [\n+                            f for f in formats\n+                            if f.get('vcodec') == 'none']\n+                        if audio_formats:\n+                            yield audio_formats[-1]\n+                    elif format_spec == 'worstaudio':\n+                        audio_formats = [\n+                            f for f in formats\n+                            if f.get('vcodec') == 'none']\n+                        if audio_formats:\n+                            yield audio_formats[0]\n+                    elif format_spec == 'bestvideo':\n+                        video_formats = [\n+                            f for f in formats\n+                            if f.get('acodec') == 'none']\n+                        if video_formats:\n+                            yield video_formats[-1]\n+                    elif format_spec == 'worstvideo':\n+                        video_formats = [\n+                            f for f in formats\n+                            if f.get('acodec') == 'none']\n+                        if video_formats:\n+                            yield video_formats[0]\n+                    else:\n+                        extensions = ['mp4', 'flv', 'webm', '3gp', 'm4a', 'mp3', 'ogg', 'aac', 'wav']\n+                        if format_spec in extensions:\n+                            filter_f = lambda f: f['ext'] == format_spec\n+                        else:\n+                            filter_f = lambda f: f['format_id'] == format_spec\n+                        matches = list(filter(filter_f, formats))\n+                        if matches:\n+                            yield matches[-1]\n+            elif selector.type == MERGE:\n+                def _merge(formats_info):\n+                    format_1, format_2 = [f['format_id'] for f in formats_info]\n+                    # The first format must contain the video and the\n+                    # second the audio\n+                    if formats_info[0].get('vcodec') == 'none':\n+                        self.report_error('The first format must '\n+                                          'contain the video, try using '\n+                                          '\"-f %s+%s\"' % (format_2, format_1))\n+                        return\n+                    output_ext = (\n+                        formats_info[0]['ext']\n+                        if self.params.get('merge_output_format') is None\n+                        else self.params['merge_output_format'])\n+                    return {\n+                        'requested_formats': formats_info,\n+                        'format': '%s+%s' % (formats_info[0].get('format'),\n+                                             formats_info[1].get('format')),\n+                        'format_id': '%s+%s' % (formats_info[0].get('format_id'),\n+                                                formats_info[1].get('format_id')),\n+                        'width': formats_info[0].get('width'),\n+                        'height': formats_info[0].get('height'),\n+                        'resolution': formats_info[0].get('resolution'),\n+                        'fps': formats_info[0].get('fps'),\n+                        'vcodec': formats_info[0].get('vcodec'),\n+                        'vbr': formats_info[0].get('vbr'),\n+                        'stretched_ratio': formats_info[0].get('stretched_ratio'),\n+                        'acodec': formats_info[1].get('acodec'),\n+                        'abr': formats_info[1].get('abr'),\n+                        'ext': output_ext,\n+                    }\n+                video_selector, audio_selector = map(_build_selector_function, selector.selector)\n \n-        new_format_spec = format_spec[:-len(m.group(0))]\n-        if not new_format_spec:\n-            new_format_spec = 'best'\n+                def selector_function(formats):\n+                    formats = list(formats)\n+                    for pair in itertools.product(video_selector(formats), audio_selector(formats)):\n+                        yield _merge(pair)\n \n-        return (new_format_spec, new_formats)\n+            filters = [self._build_format_filter(f) for f in selector.filters]\n \n-    def select_format(self, format_spec, available_formats):\n-        while format_spec.endswith(']'):\n-            format_spec, available_formats = self._apply_format_filter(\n-                format_spec, available_formats)\n-        if not available_formats:\n-            return None\n+            def final_selector(formats):\n+                for _filter in filters:\n+                    formats = list(filter(_filter, formats))","path":"youtube_dl/YoutubeDL.py","position":280,"original_position":278,"commit_id":"bb8e55366289e0c129ef85abb8c1ac1cbae86a66","original_commit_id":"cf2ac6df6896dac4d23918867bb86fac1e1088d9","user":{"login":"dstftw","id":1908898,"avatar_url":"https://avatars.githubusercontent.com/u/1908898?v=3","gravatar_id":"","url":"https://api.github.com/users/dstftw","html_url":"https://github.com/dstftw","followers_url":"https://api.github.com/users/dstftw/followers","following_url":"https://api.github.com/users/dstftw/following{/other_user}","gists_url":"https://api.github.com/users/dstftw/gists{/gist_id}","starred_url":"https://api.github.com/users/dstftw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dstftw/subscriptions","organizations_url":"https://api.github.com/users/dstftw/orgs","repos_url":"https://api.github.com/users/dstftw/repos","events_url":"https://api.github.com/users/dstftw/events{/privacy}","received_events_url":"https://api.github.com/users/dstftw/received_events","type":"User","site_admin":false},"body":"Ok, It's up to you.","created_at":"2015-07-04T20:09:16Z","updated_at":"2015-07-04T20:09:16Z","html_url":"https://github.com/rg3/youtube-dl/pull/6124#discussion_r33889725","pull_request_url":"https://api.github.com/repos/rg3/youtube-dl/pulls/6124","_links":{"self":{"href":"https://api.github.com/repos/rg3/youtube-dl/pulls/comments/33889725"},"html":{"href":"https://github.com/rg3/youtube-dl/pull/6124#discussion_r33889725"},"pull_request":{"href":"https://api.github.com/repos/rg3/youtube-dl/pulls/6124"}}},"pull_request":{"url":"https://api.github.com/repos/rg3/youtube-dl/pulls/6124","id":38762937,"html_url":"https://github.com/rg3/youtube-dl/pull/6124","diff_url":"https://github.com/rg3/youtube-dl/pull/6124.diff","patch_url":"https://github.com/rg3/youtube-dl/pull/6124.patch","issue_url":"https://api.github.com/repos/rg3/youtube-dl/issues/6124","number":6124,"state":"open","locked":false,"title":"Allow grouping specifiers in `-f`","user":{"login":"jaimeMF","id":1239727,"avatar_url":"https://avatars.githubusercontent.com/u/1239727?v=3","gravatar_id":"","url":"https://api.github.com/users/jaimeMF","html_url":"https://github.com/jaimeMF","followers_url":"https://api.github.com/users/jaimeMF/followers","following_url":"https://api.github.com/users/jaimeMF/following{/other_user}","gists_url":"https://api.github.com/users/jaimeMF/gists{/gist_id}","starred_url":"https://api.github.com/users/jaimeMF/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jaimeMF/subscriptions","organizations_url":"https://api.github.com/users/jaimeMF/orgs","repos_url":"https://api.github.com/users/jaimeMF/repos","events_url":"https://api.github.com/users/jaimeMF/events{/privacy}","received_events_url":"https://api.github.com/users/jaimeMF/received_events","type":"User","site_admin":false},"body":"Requested in #5242. For example I use `bestvideo[height<=?1080][width<=?1920][ext!=?webm]/best[height<=?1080][width<=?1920],bestaudio`, which can be written now as `(bestvideo[ext!=?webm]/best)[height<=?1080][width<=?1920],bestaudio`.\r\n\r\nSince I thought that using regexes would be hard, I decided to use `tokenize.tokenize` to split the spec in strings and operators (it also checks if there are missing brackets or parenthesis). I admit that the implementation is a bit ugly, so we may want to rethink it or clean it (maybe moving it to `utils.py`), but it works. The filters (everything inside `[]`) is still processed with the same regexes, because it's simpler and I haven't thought of an alternative implementation.\r\n\r\n@haasn your string `(bestvideo[tbr<13000][height>720]/bestvideo[tbr<13000][fps>30])+(bestaudio[ext=webm]/bestaudio)` seems to be parsed correctly, but I'd like to verify it works as expected.","created_at":"2015-06-29T11:07:28Z","updated_at":"2015-07-04T20:09:16Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2fb08cf338d6c72850e983308a524185aa0734cf","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/rg3/youtube-dl/pulls/6124/commits","review_comments_url":"https://api.github.com/repos/rg3/youtube-dl/pulls/6124/comments","review_comment_url":"https://api.github.com/repos/rg3/youtube-dl/pulls/comments{/number}","comments_url":"https://api.github.com/repos/rg3/youtube-dl/issues/6124/comments","statuses_url":"https://api.github.com/repos/rg3/youtube-dl/statuses/bb8e55366289e0c129ef85abb8c1ac1cbae86a66","head":{"label":"jaimeMF:format_spec_groups","ref":"format_spec_groups","sha":"bb8e55366289e0c129ef85abb8c1ac1cbae86a66","user":{"login":"jaimeMF","id":1239727,"avatar_url":"https://avatars.githubusercontent.com/u/1239727?v=3","gravatar_id":"","url":"https://api.github.com/users/jaimeMF","html_url":"https://github.com/jaimeMF","followers_url":"https://api.github.com/users/jaimeMF/followers","following_url":"https://api.github.com/users/jaimeMF/following{/other_user}","gists_url":"https://api.github.com/users/jaimeMF/gists{/gist_id}","starred_url":"https://api.github.com/users/jaimeMF/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jaimeMF/subscriptions","organizations_url":"https://api.github.com/users/jaimeMF/orgs","repos_url":"https://api.github.com/users/jaimeMF/repos","events_url":"https://api.github.com/users/jaimeMF/events{/privacy}","received_events_url":"https://api.github.com/users/jaimeMF/received_events","type":"User","site_admin":false},"repo":{"id":9553497,"name":"youtube-dl","full_name":"jaimeMF/youtube-dl","owner":{"login":"jaimeMF","id":1239727,"avatar_url":"https://avatars.githubusercontent.com/u/1239727?v=3","gravatar_id":"","url":"https://api.github.com/users/jaimeMF","html_url":"https://github.com/jaimeMF","followers_url":"https://api.github.com/users/jaimeMF/followers","following_url":"https://api.github.com/users/jaimeMF/following{/other_user}","gists_url":"https://api.github.com/users/jaimeMF/gists{/gist_id}","starred_url":"https://api.github.com/users/jaimeMF/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jaimeMF/subscriptions","organizations_url":"https://api.github.com/users/jaimeMF/orgs","repos_url":"https://api.github.com/users/jaimeMF/repos","events_url":"https://api.github.com/users/jaimeMF/events{/privacy}","received_events_url":"https://api.github.com/users/jaimeMF/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jaimeMF/youtube-dl","description":"Small command-line program to download videos from YouTube.com and other video sites","fork":true,"url":"https://api.github.com/repos/jaimeMF/youtube-dl","forks_url":"https://api.github.com/repos/jaimeMF/youtube-dl/forks","keys_url":"https://api.github.com/repos/jaimeMF/youtube-dl/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jaimeMF/youtube-dl/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jaimeMF/youtube-dl/teams","hooks_url":"https://api.github.com/repos/jaimeMF/youtube-dl/hooks","issue_events_url":"https://api.github.com/repos/jaimeMF/youtube-dl/issues/events{/number}","events_url":"https://api.github.com/repos/jaimeMF/youtube-dl/events","assignees_url":"https://api.github.com/repos/jaimeMF/youtube-dl/assignees{/user}","branches_url":"https://api.github.com/repos/jaimeMF/youtube-dl/branches{/branch}","tags_url":"https://api.github.com/repos/jaimeMF/youtube-dl/tags","blobs_url":"https://api.github.com/repos/jaimeMF/youtube-dl/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jaimeMF/youtube-dl/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jaimeMF/youtube-dl/git/refs{/sha}","trees_url":"https://api.github.com/repos/jaimeMF/youtube-dl/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jaimeMF/youtube-dl/statuses/{sha}","languages_url":"https://api.github.com/repos/jaimeMF/youtube-dl/languages","stargazers_url":"https://api.github.com/repos/jaimeMF/youtube-dl/stargazers","contributors_url":"https://api.github.com/repos/jaimeMF/youtube-dl/contributors","subscribers_url":"https://api.github.com/repos/jaimeMF/youtube-dl/subscribers","subscription_url":"https://api.github.com/repos/jaimeMF/youtube-dl/subscription","commits_url":"https://api.github.com/repos/jaimeMF/youtube-dl/commits{/sha}","git_commits_url":"https://api.github.com/repos/jaimeMF/youtube-dl/git/commits{/sha}","comments_url":"https://api.github.com/repos/jaimeMF/youtube-dl/comments{/number}","issue_comment_url":"https://api.github.com/repos/jaimeMF/youtube-dl/issues/comments{/number}","contents_url":"https://api.github.com/repos/jaimeMF/youtube-dl/contents/{+path}","compare_url":"https://api.github.com/repos/jaimeMF/youtube-dl/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jaimeMF/youtube-dl/merges","archive_url":"https://api.github.com/repos/jaimeMF/youtube-dl/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jaimeMF/youtube-dl/downloads","issues_url":"https://api.github.com/repos/jaimeMF/youtube-dl/issues{/number}","pulls_url":"https://api.github.com/repos/jaimeMF/youtube-dl/pulls{/number}","milestones_url":"https://api.github.com/repos/jaimeMF/youtube-dl/milestones{/number}","notifications_url":"https://api.github.com/repos/jaimeMF/youtube-dl/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jaimeMF/youtube-dl/labels{/name}","releases_url":"https://api.github.com/repos/jaimeMF/youtube-dl/releases{/id}","created_at":"2013-04-19T20:02:12Z","updated_at":"2015-01-29T13:33:16Z","pushed_at":"2015-07-04T19:59:26Z","git_url":"git://github.com/jaimeMF/youtube-dl.git","ssh_url":"git@github.com:jaimeMF/youtube-dl.git","clone_url":"https://github.com/jaimeMF/youtube-dl.git","svn_url":"https://github.com/jaimeMF/youtube-dl","homepage":"http://rg3.github.io/youtube-dl/","size":28995,"stargazers_count":3,"watchers_count":3,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":1,"mirror_url":null,"open_issues_count":0,"forks":1,"open_issues":0,"watchers":3,"default_branch":"master"}},"base":{"label":"rg3:master","ref":"master","sha":"33f1f81b8b0fe65f2b22e09537ffd58927dbb45e","user":{"login":"rg3","id":53487,"avatar_url":"https://avatars.githubusercontent.com/u/53487?v=3","gravatar_id":"","url":"https://api.github.com/users/rg3","html_url":"https://github.com/rg3","followers_url":"https://api.github.com/users/rg3/followers","following_url":"https://api.github.com/users/rg3/following{/other_user}","gists_url":"https://api.github.com/users/rg3/gists{/gist_id}","starred_url":"https://api.github.com/users/rg3/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rg3/subscriptions","organizations_url":"https://api.github.com/users/rg3/orgs","repos_url":"https://api.github.com/users/rg3/repos","events_url":"https://api.github.com/users/rg3/events{/privacy}","received_events_url":"https://api.github.com/users/rg3/received_events","type":"User","site_admin":false},"repo":{"id":1039520,"name":"youtube-dl","full_name":"rg3/youtube-dl","owner":{"login":"rg3","id":53487,"avatar_url":"https://avatars.githubusercontent.com/u/53487?v=3","gravatar_id":"","url":"https://api.github.com/users/rg3","html_url":"https://github.com/rg3","followers_url":"https://api.github.com/users/rg3/followers","following_url":"https://api.github.com/users/rg3/following{/other_user}","gists_url":"https://api.github.com/users/rg3/gists{/gist_id}","starred_url":"https://api.github.com/users/rg3/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rg3/subscriptions","organizations_url":"https://api.github.com/users/rg3/orgs","repos_url":"https://api.github.com/users/rg3/repos","events_url":"https://api.github.com/users/rg3/events{/privacy}","received_events_url":"https://api.github.com/users/rg3/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/rg3/youtube-dl","description":"Small command-line program to download videos from YouTube.com and other video sites","fork":false,"url":"https://api.github.com/repos/rg3/youtube-dl","forks_url":"https://api.github.com/repos/rg3/youtube-dl/forks","keys_url":"https://api.github.com/repos/rg3/youtube-dl/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rg3/youtube-dl/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rg3/youtube-dl/teams","hooks_url":"https://api.github.com/repos/rg3/youtube-dl/hooks","issue_events_url":"https://api.github.com/repos/rg3/youtube-dl/issues/events{/number}","events_url":"https://api.github.com/repos/rg3/youtube-dl/events","assignees_url":"https://api.github.com/repos/rg3/youtube-dl/assignees{/user}","branches_url":"https://api.github.com/repos/rg3/youtube-dl/branches{/branch}","tags_url":"https://api.github.com/repos/rg3/youtube-dl/tags","blobs_url":"https://api.github.com/repos/rg3/youtube-dl/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rg3/youtube-dl/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rg3/youtube-dl/git/refs{/sha}","trees_url":"https://api.github.com/repos/rg3/youtube-dl/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rg3/youtube-dl/statuses/{sha}","languages_url":"https://api.github.com/repos/rg3/youtube-dl/languages","stargazers_url":"https://api.github.com/repos/rg3/youtube-dl/stargazers","contributors_url":"https://api.github.com/repos/rg3/youtube-dl/contributors","subscribers_url":"https://api.github.com/repos/rg3/youtube-dl/subscribers","subscription_url":"https://api.github.com/repos/rg3/youtube-dl/subscription","commits_url":"https://api.github.com/repos/rg3/youtube-dl/commits{/sha}","git_commits_url":"https://api.github.com/repos/rg3/youtube-dl/git/commits{/sha}","comments_url":"https://api.github.com/repos/rg3/youtube-dl/comments{/number}","issue_comment_url":"https://api.github.com/repos/rg3/youtube-dl/issues/comments{/number}","contents_url":"https://api.github.com/repos/rg3/youtube-dl/contents/{+path}","compare_url":"https://api.github.com/repos/rg3/youtube-dl/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rg3/youtube-dl/merges","archive_url":"https://api.github.com/repos/rg3/youtube-dl/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rg3/youtube-dl/downloads","issues_url":"https://api.github.com/repos/rg3/youtube-dl/issues{/number}","pulls_url":"https://api.github.com/repos/rg3/youtube-dl/pulls{/number}","milestones_url":"https://api.github.com/repos/rg3/youtube-dl/milestones{/number}","notifications_url":"https://api.github.com/repos/rg3/youtube-dl/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rg3/youtube-dl/labels{/name}","releases_url":"https://api.github.com/repos/rg3/youtube-dl/releases{/id}","created_at":"2010-10-31T14:35:07Z","updated_at":"2015-07-04T17:55:07Z","pushed_at":"2015-07-04T19:59:30Z","git_url":"git://github.com/rg3/youtube-dl.git","ssh_url":"git@github.com:rg3/youtube-dl.git","clone_url":"https://github.com/rg3/youtube-dl.git","svn_url":"https://github.com/rg3/youtube-dl","homepage":"http://rg3.github.io/youtube-dl/","size":77363,"stargazers_count":10261,"watchers_count":10261,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":2058,"mirror_url":null,"open_issues_count":1154,"forks":2058,"open_issues":1154,"watchers":10261,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/rg3/youtube-dl/pulls/6124"},"html":{"href":"https://github.com/rg3/youtube-dl/pull/6124"},"issue":{"href":"https://api.github.com/repos/rg3/youtube-dl/issues/6124"},"comments":{"href":"https://api.github.com/repos/rg3/youtube-dl/issues/6124/comments"},"review_comments":{"href":"https://api.github.com/repos/rg3/youtube-dl/pulls/6124/comments"},"review_comment":{"href":"https://api.github.com/repos/rg3/youtube-dl/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/rg3/youtube-dl/pulls/6124/commits"},"statuses":{"href":"https://api.github.com/repos/rg3/youtube-dl/statuses/bb8e55366289e0c129ef85abb8c1ac1cbae86a66"}}}}