{"action":"created","comment":{"url":"https://api.github.com/repos/sampsyo/beets/pulls/comments/44225879","id":44225879,"diff_hunk":"@@ -0,0 +1,438 @@\n+# This file is part of beets.\n+# Copyright 2015\n+#\n+# Permission is hereby granted, free of charge, to any person obtaining\n+# a copy of this software and associated documentation files (the\n+# \"Software\"), to deal in the Software without restriction, including\n+# without limitation the rights to use, copy, modify, merge, publish,\n+# distribute, sublicense, and/or sell copies of the Software, and to\n+# permit persons to whom the Software is furnished to do so, subject to\n+# the following conditions:\n+#\n+# The above copyright notice and this permission notice shall be\n+# included in all copies or substantial portions of the Software.\n+\n+\"\"\"Open metadata information in a text editor to let the user change\n+them directly.\n+\"\"\"\n+from __future__ import (division, absolute_import, print_function,\n+                        unicode_literals)\n+\n+from beets import plugins\n+from beets.ui import Subcommand, decargs, library, print_\n+from beets.ui.commands import _do_query\n+import subprocess\n+import difflib\n+import yaml\n+import collections\n+import webbrowser\n+from sys import exit\n+from beets import config\n+from beets import ui\n+from tempfile import NamedTemporaryFile\n+import os\n+\n+\n+class EditPlugin(plugins.BeetsPlugin):\n+\n+    def __init__(self):\n+        super(EditPlugin, self).__init__()\n+\n+        self.config.add({\n+            'style': 'yaml',\n+            'editor': '',\n+            'diff_method': '',\n+            'browser': '',\n+            'albumfields': 'album albumartist',\n+            'itemfields': 'track title artist album',\n+            'not_fields': 'id path',\n+            'sep': '-',\n+        })\n+\n+        self.style = self.config['style'].get(unicode)\n+\n+        # The editor field in the config lets you specify your editor.\n+        # Defaults to open with webrowser module.\n+        self.editor = self.config['editor'].as_str_seq()\n+\n+        # the html_viewer field in your config lets you specify\n+        # your htmlviewer. Defaults to open with webrowser module\n+        self.browser = self.config['browser'].as_str_seq()\n+\n+        # the diff_method field in your config picks the way to see your\n+        # changes. Options are:\n+        # 'ndiff'(2 files with differences),\n+        # 'unified'(just the different lines and a few lines of context),\n+        # 'html'(view in html-format),\n+        # 'vimdiff'(view in VIM)\n+        self.diff_method = self.config['diff_method'].get(unicode)\n+\n+        # the albumfields field in your config sets the tags that\n+        # you want to see/change for albums.\n+        # Defaults to album albumartist.\n+        # the ID tag will always be listed as it is used to identify the item\n+        self.albumfields = self.config['albumfields'].as_str_seq()\n+\n+        # the itemfields field in your config sets the tags that\n+        # you want to see/change or items.\n+        # Defaults to track title artist album.\n+        # the ID tag will always be listed as it is used to identify the item\n+        self.itemfields = self.config['itemfields'].as_str_seq()\n+\n+        # the not_fields field in your config sets the tags that\n+        # will not be changed.\n+        # If you happen to change them, they will be restored to the original\n+        # value. The ID of an item will never be changed.\n+        self.not_fields = self.config['not_fields'].as_str_seq()\n+\n+        # the separator in your config sets the separator that will be used\n+        # between fields in your terminal. Defaults to -\n+        self.sep = self.config['sep'].get(unicode)\n+\n+        self.ed = None\n+        self.ed_args = None\n+        self.brw = None\n+        self.brw_args = None\n+\n+    def commands(self):\n+        edit_command = Subcommand(\n+            'edit',\n+            help='interactively edit metadata')\n+        edit_command.parser.add_option(\n+            '-e', '--extra',\n+            action='store',\n+            help='add additional fields to edit',\n+        )\n+        edit_command.parser.add_option(\n+            '--all',\n+            action='store_true', dest='all',\n+            help='edit all fields',\n+        )\n+        edit_command.parser.add_option(\n+            '--sum',\n+            action='store_true', dest='sum',\n+            help='list fields with the same value',\n+        )\n+        edit_command.parser.add_all_common_options()\n+        edit_command.func = self.editor_music\n+        return[edit_command]\n+\n+    def editor_music(self, lib, opts, args):\n+        if self.editor:\n+            self.ed_args = self.editor[1:] if len(self.editor) > 1 else None\n+            self.ed = self.editor[0] if self.editor else None\n+        if self.browser:\n+            self.brw_args = self.browser[1:] if len(self.browser) > 1 else None\n+            self.brw = self.browser[0] if self.browser else None\n+\n+        # Get the objects to edit.\n+        query = decargs(args)\n+        items, albums = _do_query(lib, query, opts.album, False)\n+        objs = albums if opts.album else items\n+        if not objs:\n+            print_('Nothing to edit.')\n+            return\n+\n+        # edit tags in a textfile in yaml-style\n+        # makes a string representation of an object\n+        # for now yaml but we could add html,pprint,toml\n+        self.print_items = {\n+            'yaml': self.print_to_yaml}\n+        # makes an object from a string representation\n+        # for now yaml but we could add html,pprint,toml\n+        self.string_to_dict = {\n+            'yaml': self.yaml_to_dict}\n+        # 4 ways to view the changes in objects\n+        self.diffresults = {\n+            'ndiff': self.ndiff,\n+            'unified': self.unified,\n+            'html': self.html,\n+            'vimdiff': self.vimdiff}\n+        # make a dictionary from the chosen fields\n+        # you can do em all or a selection\n+        self.make_dict = {\n+            'all': self.get_all_fields,\n+            \"selected\": self.get_selected_fields}\n+\n+        self.get_fields_from(objs, opts)\n+\n+        # Confirm.\n+        for obj in objs:\n+            print_(format(obj))\n+        if not ui.input_yn(ui.colorize('action_default', \"Edit? (y/n)\"), True):\n+            return\n+\n+        dict_from_objs = self.make_dict[self.pick](self.fields, objs, opts)\n+        newyaml, oldyaml = self.change_objs(dict_from_objs)\n+        changed_objs = self.check_diff(newyaml, oldyaml, opts)\n+        if not changed_objs:\n+            print_(\"nothing to change\")\n+            return\n+        self.save_items(changed_objs, lib, opts)\n+\n+    def print_to_yaml(self, arg):\n+        # from object to yaml\n+        return yaml.safe_dump_all(\n+            arg,\n+            allow_unicode=True,\n+            default_flow_style=False)\n+\n+    def yaml_to_dict(self, yam):\n+        # from yaml to object\n+        return yaml.load_all(yam)\n+\n+    def get_fields_from(self, objs, opts):\n+        # construct a list of fields we need\n+        # see if we need album or item fields\n+        self.fields = self.albumfields if opts.album else self.itemfields\n+        # if opts.extra is given add those\n+        if opts.extra:\n+            fi = (opts.extra).replace('$', \"\").split()\n+            self.fields.extend([f for f in fi if f not in self.fields])\n+        # make sure we got the id for identification\n+        if 'id' not in self.fields:\n+            self.fields.insert(0, 'id')\n+        # we need all the fields\n+        if opts.all:\n+            self.fields = None\n+            self.pick = \"all\"\n+            print_(ui.colorize('text_warning', \"edit all fields from:\"))\n+        else:\n+            for it in self.fields:\n+                if opts.album:\n+                    if it not in library.Album.all_keys():\n+                        print_(\n+                            \"{} not in albumfields.Removed it.\".format(\n+                                ui.colorize(\n+                                    'text_warning', it)))\n+                        self.fields.remove(it)\n+                else:\n+                    if it not in library.Item.all_keys():\n+                        print_(\n+                            \"{} not in itemfields.Removed it.\".format(\n+                                ui.colorize(\n+                                    'text_warning', it)))\n+                        self.fields.remove(it)\n+            self.pick = \"selected\"\n+\n+    def get_selected_fields(self, myfields, objs, opts):\n+        a = []\n+        if opts.sum:\n+            for field in myfields:\n+                if field not in self.not_fields:\n+                    d = collections.defaultdict(str)\n+                    for obj in objs:\n+                        d[obj[field]] += (\" \" + str(obj['id']))\n+                    a.append([field, [{f: i} for f, i in d.items()]])\n+            return a\n+        else:\n+            for mod in objs:\n+                a.append([{fi: mod[fi]}for fi in myfields])\n+            return a\n+\n+    def get_all_fields(self, myfields, objs, opts):\n+        a = []\n+        if opts.sum:\n+            fields = (library.Album.all_keys() if opts.album\n+                      else library.Item.all_keys())\n+            for field in sorted(fields):\n+                # for every field get a dict\n+                d = collections.defaultdict(str)\n+                for obj in objs:\n+                    # put all the ob-ids in the dict[field] as a string\n+                    d[obj[field]] += (\" \" + str(obj['id']))\n+                # for the field, we get the value and the users\n+                a.append([field, [{f: i} for f, i in sorted(d.items())]])\n+            return a\n+        else:\n+            for mod in objs:\n+                a.append([{fi: mod[fi]} for fi in sorted(mod._fields)])\n+            return a\n+\n+    def change_objs(self, dict_items):\n+        # construct a yaml from the original object-fields\n+        # and make a yaml that we can change in the text-editor\n+        oldyaml = self.print_items[self.style](dict_items)\n+        newyaml = self.print_items[self.style](dict_items)\n+        new = NamedTemporaryFile(suffix='.yaml', delete=False)\n+        new.write(newyaml)\n+        new.close()\n+        self.get_editor(new.name)\n+\n+        if ui.input_yn(ui.colorize('action_default', \"done?(y)\"), True):\n+            while True:\n+                try:\n+                    with open(new.name) as f:\n+                        newyaml = f.read()\n+                        list(yaml.load_all(newyaml))\n+                        break\n+                except yaml.YAMLError as e:\n+                    print_(ui.colorize('text_warning',\n+                           \"change this fault: {}\".format(e)))\n+                    print_(\"correct format for empty = - '' :\")\n+                    if ui.input_yn(\n+                            ui.colorize('action_default', \"fix?(y)\"), True):\n+                        self.get_editor(new.name)\n+                        if ui.input_yn(ui.colorize(\n+                           'action_default', \"ok.fixed.(y)\"), True):\n+                            pass\n+\n+            return newyaml, oldyaml\n+        else:\n+            exit()\n+\n+    def get_editor(self, name):\n+        if not self.ed:\n+            editor = os.getenv('EDITOR')\n+            if editor:\n+                os.system(editor + \" \" + name)\n+            else:\n+                webbrowser.open(name, new=2, autoraise=True)","path":"beetsplug/edit.py","position":290,"original_position":290,"commit_id":"331ced3cca607d1ea9a8255a054c5b8f67adb77f","original_commit_id":"331ced3cca607d1ea9a8255a054c5b8f67adb77f","user":{"login":"diego-plan9","id":418311,"avatar_url":"https://avatars.githubusercontent.com/u/418311?v=3","gravatar_id":"","url":"https://api.github.com/users/diego-plan9","html_url":"https://github.com/diego-plan9","followers_url":"https://api.github.com/users/diego-plan9/followers","following_url":"https://api.github.com/users/diego-plan9/following{/other_user}","gists_url":"https://api.github.com/users/diego-plan9/gists{/gist_id}","starred_url":"https://api.github.com/users/diego-plan9/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/diego-plan9/subscriptions","organizations_url":"https://api.github.com/users/diego-plan9/orgs","repos_url":"https://api.github.com/users/diego-plan9/repos","events_url":"https://api.github.com/users/diego-plan9/events{/privacy}","received_events_url":"https://api.github.com/users/diego-plan9/received_events","type":"User","site_admin":false},"body":"Just a quick question in regards to the safety of using `webbrowser.open` I came across while working on my previous set of changes: has it been reasonably tested that it really produces the desired results? Specially, considering the remark on [the python docs](https://docs.python.org/2/library/webbrowser.html#webbrowser.open):\r\n\r\n>  Note that on some platforms, trying to open a filename using this function, may work and start the operating system’s associated program. However, this is neither supported nor portable.\r\n\r\nFor example, in my home machine (debian 8 + libreoffice without too much tuning), seems that debian by default does not add the `$EDITOR` variable to the environment, and as a consequence `webbrowser.open` launchs libreoffice writer.\r\n\r\nIf associating .yaml files with vim via `$ xdg-mime default vim.desktop application/x-yaml`, webbrowser seems to exhibit some weird behaviour, launching vim but not replying to stdin correctly and resulting in an unresponsive vim - after killing it manually, it launches libreoffice once again. Might be worth devoting some time to check it more carefully, as it might affect more users and the `config` command as well?\r\n\r\nPerhaps it would be enough to add a clearer reminder to the users on the documentation to set `$EDITOR`, to avoid nuisances? It's already on http://beets.readthedocs.org/en/v1.3.15/reference/cli.html#config anyway, but might not hurt.","created_at":"2015-11-08T15:08:16Z","updated_at":"2015-11-08T15:08:16Z","html_url":"https://github.com/sampsyo/beets/pull/1706#discussion_r44225879","pull_request_url":"https://api.github.com/repos/sampsyo/beets/pulls/1706","_links":{"self":{"href":"https://api.github.com/repos/sampsyo/beets/pulls/comments/44225879"},"html":{"href":"https://github.com/sampsyo/beets/pull/1706#discussion_r44225879"},"pull_request":{"href":"https://api.github.com/repos/sampsyo/beets/pulls/1706"}}},"pull_request":{"url":"https://api.github.com/repos/sampsyo/beets/pulls/1706","id":50044483,"html_url":"https://github.com/sampsyo/beets/pull/1706","diff_url":"https://github.com/sampsyo/beets/pull/1706.diff","patch_url":"https://github.com/sampsyo/beets/pull/1706.patch","issue_url":"https://api.github.com/repos/sampsyo/beets/issues/1706","number":1706,"state":"open","locked":false,"title":"Edit plugin","user":{"login":"sampsyo","id":188033,"avatar_url":"https://avatars.githubusercontent.com/u/188033?v=3","gravatar_id":"","url":"https://api.github.com/users/sampsyo","html_url":"https://github.com/sampsyo","followers_url":"https://api.github.com/users/sampsyo/followers","following_url":"https://api.github.com/users/sampsyo/following{/other_user}","gists_url":"https://api.github.com/users/sampsyo/gists{/gist_id}","starred_url":"https://api.github.com/users/sampsyo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sampsyo/subscriptions","organizations_url":"https://api.github.com/users/sampsyo/orgs","repos_url":"https://api.github.com/users/sampsyo/repos","events_url":"https://api.github.com/users/sampsyo/events{/privacy}","received_events_url":"https://api.github.com/users/sampsyo/received_events","type":"User","site_admin":false},"body":"Successor to #1687. This is @jmwatte's interactive editor plugin.","created_at":"2015-11-07T20:24:33Z","updated_at":"2015-11-08T15:08:16Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7375b3699b7be32a79214e6ac26b9b812fff95ac","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/sampsyo/beets/pulls/1706/commits","review_comments_url":"https://api.github.com/repos/sampsyo/beets/pulls/1706/comments","review_comment_url":"https://api.github.com/repos/sampsyo/beets/pulls/comments{/number}","comments_url":"https://api.github.com/repos/sampsyo/beets/issues/1706/comments","statuses_url":"https://api.github.com/repos/sampsyo/beets/statuses/331ced3cca607d1ea9a8255a054c5b8f67adb77f","head":{"label":"sampsyo:editor","ref":"editor","sha":"331ced3cca607d1ea9a8255a054c5b8f67adb77f","user":{"login":"sampsyo","id":188033,"avatar_url":"https://avatars.githubusercontent.com/u/188033?v=3","gravatar_id":"","url":"https://api.github.com/users/sampsyo","html_url":"https://github.com/sampsyo","followers_url":"https://api.github.com/users/sampsyo/followers","following_url":"https://api.github.com/users/sampsyo/following{/other_user}","gists_url":"https://api.github.com/users/sampsyo/gists{/gist_id}","starred_url":"https://api.github.com/users/sampsyo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sampsyo/subscriptions","organizations_url":"https://api.github.com/users/sampsyo/orgs","repos_url":"https://api.github.com/users/sampsyo/repos","events_url":"https://api.github.com/users/sampsyo/events{/privacy}","received_events_url":"https://api.github.com/users/sampsyo/received_events","type":"User","site_admin":false},"repo":{"id":827590,"name":"beets","full_name":"sampsyo/beets","owner":{"login":"sampsyo","id":188033,"avatar_url":"https://avatars.githubusercontent.com/u/188033?v=3","gravatar_id":"","url":"https://api.github.com/users/sampsyo","html_url":"https://github.com/sampsyo","followers_url":"https://api.github.com/users/sampsyo/followers","following_url":"https://api.github.com/users/sampsyo/following{/other_user}","gists_url":"https://api.github.com/users/sampsyo/gists{/gist_id}","starred_url":"https://api.github.com/users/sampsyo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sampsyo/subscriptions","organizations_url":"https://api.github.com/users/sampsyo/orgs","repos_url":"https://api.github.com/users/sampsyo/repos","events_url":"https://api.github.com/users/sampsyo/events{/privacy}","received_events_url":"https://api.github.com/users/sampsyo/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/sampsyo/beets","description":"music library manager and MusicBrainz tagger","fork":false,"url":"https://api.github.com/repos/sampsyo/beets","forks_url":"https://api.github.com/repos/sampsyo/beets/forks","keys_url":"https://api.github.com/repos/sampsyo/beets/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sampsyo/beets/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sampsyo/beets/teams","hooks_url":"https://api.github.com/repos/sampsyo/beets/hooks","issue_events_url":"https://api.github.com/repos/sampsyo/beets/issues/events{/number}","events_url":"https://api.github.com/repos/sampsyo/beets/events","assignees_url":"https://api.github.com/repos/sampsyo/beets/assignees{/user}","branches_url":"https://api.github.com/repos/sampsyo/beets/branches{/branch}","tags_url":"https://api.github.com/repos/sampsyo/beets/tags","blobs_url":"https://api.github.com/repos/sampsyo/beets/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sampsyo/beets/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sampsyo/beets/git/refs{/sha}","trees_url":"https://api.github.com/repos/sampsyo/beets/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sampsyo/beets/statuses/{sha}","languages_url":"https://api.github.com/repos/sampsyo/beets/languages","stargazers_url":"https://api.github.com/repos/sampsyo/beets/stargazers","contributors_url":"https://api.github.com/repos/sampsyo/beets/contributors","subscribers_url":"https://api.github.com/repos/sampsyo/beets/subscribers","subscription_url":"https://api.github.com/repos/sampsyo/beets/subscription","commits_url":"https://api.github.com/repos/sampsyo/beets/commits{/sha}","git_commits_url":"https://api.github.com/repos/sampsyo/beets/git/commits{/sha}","comments_url":"https://api.github.com/repos/sampsyo/beets/comments{/number}","issue_comment_url":"https://api.github.com/repos/sampsyo/beets/issues/comments{/number}","contents_url":"https://api.github.com/repos/sampsyo/beets/contents/{+path}","compare_url":"https://api.github.com/repos/sampsyo/beets/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sampsyo/beets/merges","archive_url":"https://api.github.com/repos/sampsyo/beets/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sampsyo/beets/downloads","issues_url":"https://api.github.com/repos/sampsyo/beets/issues{/number}","pulls_url":"https://api.github.com/repos/sampsyo/beets/pulls{/number}","milestones_url":"https://api.github.com/repos/sampsyo/beets/milestones{/number}","notifications_url":"https://api.github.com/repos/sampsyo/beets/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sampsyo/beets/labels{/name}","releases_url":"https://api.github.com/repos/sampsyo/beets/releases{/id}","created_at":"2010-08-09T23:17:20Z","updated_at":"2015-11-08T00:49:20Z","pushed_at":"2015-11-07T23:38:01Z","git_url":"git://github.com/sampsyo/beets.git","ssh_url":"git@github.com:sampsyo/beets.git","clone_url":"https://github.com/sampsyo/beets.git","svn_url":"https://github.com/sampsyo/beets","homepage":"http://beets.radbox.org/","size":27323,"stargazers_count":4257,"watchers_count":4257,"language":"Python","has_issues":true,"has_downloads":false,"has_wiki":true,"has_pages":true,"forks_count":756,"mirror_url":null,"open_issues_count":346,"forks":756,"open_issues":346,"watchers":4257,"default_branch":"master"}},"base":{"label":"sampsyo:master","ref":"master","sha":"cb6edb46efe8380990fe0ff6718204ad66ad3f5d","user":{"login":"sampsyo","id":188033,"avatar_url":"https://avatars.githubusercontent.com/u/188033?v=3","gravatar_id":"","url":"https://api.github.com/users/sampsyo","html_url":"https://github.com/sampsyo","followers_url":"https://api.github.com/users/sampsyo/followers","following_url":"https://api.github.com/users/sampsyo/following{/other_user}","gists_url":"https://api.github.com/users/sampsyo/gists{/gist_id}","starred_url":"https://api.github.com/users/sampsyo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sampsyo/subscriptions","organizations_url":"https://api.github.com/users/sampsyo/orgs","repos_url":"https://api.github.com/users/sampsyo/repos","events_url":"https://api.github.com/users/sampsyo/events{/privacy}","received_events_url":"https://api.github.com/users/sampsyo/received_events","type":"User","site_admin":false},"repo":{"id":827590,"name":"beets","full_name":"sampsyo/beets","owner":{"login":"sampsyo","id":188033,"avatar_url":"https://avatars.githubusercontent.com/u/188033?v=3","gravatar_id":"","url":"https://api.github.com/users/sampsyo","html_url":"https://github.com/sampsyo","followers_url":"https://api.github.com/users/sampsyo/followers","following_url":"https://api.github.com/users/sampsyo/following{/other_user}","gists_url":"https://api.github.com/users/sampsyo/gists{/gist_id}","starred_url":"https://api.github.com/users/sampsyo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sampsyo/subscriptions","organizations_url":"https://api.github.com/users/sampsyo/orgs","repos_url":"https://api.github.com/users/sampsyo/repos","events_url":"https://api.github.com/users/sampsyo/events{/privacy}","received_events_url":"https://api.github.com/users/sampsyo/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/sampsyo/beets","description":"music library manager and MusicBrainz tagger","fork":false,"url":"https://api.github.com/repos/sampsyo/beets","forks_url":"https://api.github.com/repos/sampsyo/beets/forks","keys_url":"https://api.github.com/repos/sampsyo/beets/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sampsyo/beets/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sampsyo/beets/teams","hooks_url":"https://api.github.com/repos/sampsyo/beets/hooks","issue_events_url":"https://api.github.com/repos/sampsyo/beets/issues/events{/number}","events_url":"https://api.github.com/repos/sampsyo/beets/events","assignees_url":"https://api.github.com/repos/sampsyo/beets/assignees{/user}","branches_url":"https://api.github.com/repos/sampsyo/beets/branches{/branch}","tags_url":"https://api.github.com/repos/sampsyo/beets/tags","blobs_url":"https://api.github.com/repos/sampsyo/beets/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sampsyo/beets/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sampsyo/beets/git/refs{/sha}","trees_url":"https://api.github.com/repos/sampsyo/beets/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sampsyo/beets/statuses/{sha}","languages_url":"https://api.github.com/repos/sampsyo/beets/languages","stargazers_url":"https://api.github.com/repos/sampsyo/beets/stargazers","contributors_url":"https://api.github.com/repos/sampsyo/beets/contributors","subscribers_url":"https://api.github.com/repos/sampsyo/beets/subscribers","subscription_url":"https://api.github.com/repos/sampsyo/beets/subscription","commits_url":"https://api.github.com/repos/sampsyo/beets/commits{/sha}","git_commits_url":"https://api.github.com/repos/sampsyo/beets/git/commits{/sha}","comments_url":"https://api.github.com/repos/sampsyo/beets/comments{/number}","issue_comment_url":"https://api.github.com/repos/sampsyo/beets/issues/comments{/number}","contents_url":"https://api.github.com/repos/sampsyo/beets/contents/{+path}","compare_url":"https://api.github.com/repos/sampsyo/beets/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sampsyo/beets/merges","archive_url":"https://api.github.com/repos/sampsyo/beets/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sampsyo/beets/downloads","issues_url":"https://api.github.com/repos/sampsyo/beets/issues{/number}","pulls_url":"https://api.github.com/repos/sampsyo/beets/pulls{/number}","milestones_url":"https://api.github.com/repos/sampsyo/beets/milestones{/number}","notifications_url":"https://api.github.com/repos/sampsyo/beets/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sampsyo/beets/labels{/name}","releases_url":"https://api.github.com/repos/sampsyo/beets/releases{/id}","created_at":"2010-08-09T23:17:20Z","updated_at":"2015-11-08T00:49:20Z","pushed_at":"2015-11-07T23:38:01Z","git_url":"git://github.com/sampsyo/beets.git","ssh_url":"git@github.com:sampsyo/beets.git","clone_url":"https://github.com/sampsyo/beets.git","svn_url":"https://github.com/sampsyo/beets","homepage":"http://beets.radbox.org/","size":27323,"stargazers_count":4257,"watchers_count":4257,"language":"Python","has_issues":true,"has_downloads":false,"has_wiki":true,"has_pages":true,"forks_count":756,"mirror_url":null,"open_issues_count":346,"forks":756,"open_issues":346,"watchers":4257,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/sampsyo/beets/pulls/1706"},"html":{"href":"https://github.com/sampsyo/beets/pull/1706"},"issue":{"href":"https://api.github.com/repos/sampsyo/beets/issues/1706"},"comments":{"href":"https://api.github.com/repos/sampsyo/beets/issues/1706/comments"},"review_comments":{"href":"https://api.github.com/repos/sampsyo/beets/pulls/1706/comments"},"review_comment":{"href":"https://api.github.com/repos/sampsyo/beets/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/sampsyo/beets/pulls/1706/commits"},"statuses":{"href":"https://api.github.com/repos/sampsyo/beets/statuses/331ced3cca607d1ea9a8255a054c5b8f67adb77f"}}}}