GitHub PullRequestReviewCommentEvent at 2014-11-18 21:41:18
Repository: freeseer
Actor: zxiiro
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/Freeseer/freeseer/pulls/comments/20540558","id":20540558,"diff_hunk":"@@ -22,103 +22,237 @@\n # For support, questions, suggestions or any other inquiries, visit:\n # http://wiki.github.com/Freeseer/freeseer/\n \n-import shutil\n-import tempfile\n-import unittest\n-\n import pygst\n-pygst.require(\"0.10\")\n+pygst.require(\"0.10\")  # required before import gst\n+\n+from collections import defaultdict\n+from functools import partial\n import gst\n+import os\n+import pytest\n+import re\n+import sys\n \n from freeseer.framework.config.profile import ProfileManager\n from freeseer.framework.plugin import PluginManager\n \n \n-class TestPlugins(unittest.TestCase):\n+@pytest.fixture\n+def plugin_manager(tmpdir):\n+    \"\"\"Constructs a plugin manager using a fake profile with a temporary directory.\"\"\"\n+    profile_manager = ProfileManager(str(tmpdir))\n+    profile = profile_manager.get('testing')\n+    return PluginManager(profile)\n+\n+\n+@pytest.fixture(scope='module')\n+def plugin_platform_category_cache():\n+    \"\"\"\n+    Test Helper fixture used to create a count of the number of plugins a platform supports by category\n+    \"\"\"\n+    platforms = ['linux', 'linux2', 'win32', 'cygwin', 'darwin', 'fake-dos']\n+    pluginpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, \"plugins\")\n+\n+    categories = []\n+    # Categories are folder names in the plugins directory such as 'audioinput', 'audiomixer', etc.\n+    for directory in os.listdir(pluginpath):\n+        if os.path.isdir(os.path.join(pluginpath, directory)):\n+            categories.append(directory)\n+\n+    plugin_platform_category_count = defaultdict(partial(defaultdict, int))  # {platform : {category : count, category2 : count2}}\n+\n+    for category in categories:\n+        for root, directories, files in os.walk(os.path.join(pluginpath, category)):\n+            for filename in files:\n+                if filename.endswith('.freeseer-plugin'):\n+                    plugin_name, _ = os.path.splitext(filename)\n+\n+                    if os.path.isfile(os.path.join(root, '{}.py'.format(plugin_name))):\n+                        # Case one: the plugin name is plugin.py\n+                        plugin_file_name = os.path.join(root, '{}.py'.format(plugin_name))\n+                    elif os.path.isfile(os.path.join(root, plugin_name, '__init__.py')):\n+                        # Case two: the plugin is in the __init__.py file of the directory named plugin\n+                        plugin_file_name = os.path.join(root, plugin_name, '__init__.py')\n+                    else:\n+                        # Case three, we could not find the plugin in the expected locations.\n+                        assert False, 'Failed to find plugin file but saw plugin.freeseer-plugin file.'\n+\n+                    with open(plugin_file_name, 'r') as search_file:\n+                        for line in search_file:\n+                            if re.match('\\s*os =.*', line):  # find a string like \" os = ['win32',..,'platforms']\"\n+                                for platform in platforms:\n+                                    if platform in line:\n+                                        plugin_platform_category_count[platform][category] += 1\n+    return plugin_platform_category_count\n+\n+\n+@pytest.mark.parametrize('plugin_info, expected_instance', [\n+    (('AudioInput', 'get_audioinput_bin'), gst.Bin),\n+    (('AudioMixer', 'get_audiomixer_bin'), gst.Bin),\n+    (('VideoInput', 'get_videoinput_bin'), gst.Bin),\n+    (('VideoMixer', 'get_videomixer_bin'), gst.Bin),\n+    (('Output', 'get_output_bin'), gst.Bin)\n+])\n+def test_plugin_bin(plugin_manager, plugin_info, expected_instance):\n+    \"\"\"Check that a plugin and its get method returns the proper gst.Bin object\"\"\"\n+    plugin_name, get_plugin_method = plugin_info\n+    plugins = plugin_manager.get_plugins_of_category(plugin_name)\n+\n+    for plugin in plugins:\n+        plugin.plugin_object.load_config(plugin_manager)\n+        if plugin.name == \"Firewire Source\":\n+            # FIXME: There is a link error in firewiresrc/__init__.py on the line:\n+            # dv1394dvdemux.link(dv1394q2)\n+            # Related gh#141\n+            # Issue gh#644\n+            continue\n+        plugin_bin = getattr(plugin.plugin_object, get_plugin_method)()\n+        assert isinstance(plugin_bin, expected_instance)\n+\n+\n+def test_os_supported(plugin_manager, monkeypatch):\n+    \"\"\"\n+    Verifies that the os_supported function returns true when a plugin claims to support a given OS.\n+    Verifies that the os_supported function returns false when a fake operating system is given.\n+    \"\"\"\n+    plugins = plugin_manager.get_all_plugins()\n+    assert plugins, \"The list of plugins should not be empty.\"\n+    for plugin in plugins:\n+        if 'linux2' in plugin.plugin_object.os:\n+            monkeypatch.setattr(sys, 'platform', 'linux2')\n+            assert plugin_manager._os_supported(plugin)\n+        if 'darwin' in plugin.plugin_object.os:\n+            monkeypatch.setattr(sys, 'platform', 'darwin')\n+            assert plugin_manager._os_supported(plugin)\n+        if 'win32' in plugin.plugin_object.os:\n+            monkeypatch.setattr(sys, 'platform', 'win32')\n+            assert plugin_manager._os_supported(plugin)\n+\n+        monkeypatch.setattr(sys, 'platform', 'fake-dos')\n+        assert not plugin_manager._os_supported(plugin)  # Fake os should not be supported\n+\n+\n+def test_get_supported_plugins(plugin_manager, monkeypatch):\n+    \"\"\"\n+    Verifies per platform that all plugins returned are indeed supported.\n+    Also checks that an expected number of plugins are returned.\n+    \"\"\"\n+    unfiltered_plugins = plugin_manager.plugmanc.getAllPlugins()\n+    monkeypatch.setattr(sys, 'platform', 'linux2')\n+    linux_plugins = plugin_manager._get_supported_plugins(unfiltered_plugins)\n+    assert linux_plugins\n+    for plugin in linux_plugins:\n+        assert 'linux2' in plugin.plugin_object.os\n \n-    def setUp(self):\n-        '''\n-        Stardard init method: runs before each test_* method\n+    monkeypatch.setattr(sys, 'platform', 'darwin')\n+    darwin_plugins = plugin_manager._get_supported_plugins(unfiltered_plugins)\n+    assert darwin_plugins\n+    for plugin in darwin_plugins:\n+        assert 'darwin' in plugin.plugin_object.os\n \n-        Initializes a PluginManager\n+    monkeypatch.setattr(sys, 'platform', 'win32')\n+    win32_plugins = plugin_manager._get_supported_plugins(unfiltered_plugins)\n+    assert win32_plugins\n+    for plugin in win32_plugins:\n+        assert 'win32' in plugin.plugin_object.os\n \n-        '''\n-        self.profile_manager = ProfileManager(tempfile.mkdtemp())\n-        profile = self.profile_manager.get('testing')\n-        self.plugman = PluginManager(profile)\n+    monkeypatch.setattr(sys, 'platform', 'fake-dos')\n+    fakeos_plugins = plugin_manager._get_supported_plugins(unfiltered_plugins)\n+    assert not fakeos_plugins\n \n-    def tearDown(self):\n-        '''\n-        Generic unittest.TestCase.tearDown()\n-        '''\n-        shutil.rmtree(self.profile_manager._base_folder)\n \n-    def test_audio_input_bin(self):\n-        '''Check that audio input plugins are returning a gst.Bin object\n+@pytest.mark.parametrize('plugin_name, plugin_category', [\n+    ('Audio Feedback', 'Output'),\n+    ('Video Preview', 'Output'),\n+    ('Rss FeedParser', 'Importer'),\n+    ('CSV Importer', 'Importer')\n+])\n+def test_get_plugin_by_name(plugin_manager, plugin_name, plugin_category):\n+    \"\"\"\n+    Verify that the get_plugin_by_name function returns given existing functions.\n+    \"\"\"\n+    plugin = plugin_manager.get_plugin_by_name(plugin_name, plugin_category)\n+    assert plugin.plugin_object\n+    assert plugin.plugin_object.name == plugin_name\n+    assert plugin.plugin_object.CATEGORY == plugin_category\n \n-        Verifies that get_audioinput_bin() is returning the proper object.\n-        '''\n-        plugins = self.plugman.get_plugins_of_category(\"AudioInput\")\n \n-        for plugin in plugins:\n-            plugin.plugin_object.load_config(self.plugman)\n-            plugin_bin = plugin.plugin_object.get_audioinput_bin()\n-            self.assertIsInstance(plugin_bin, gst.Bin,\n-                \"%s did not return a gst.Bin object\" % plugin.name)\n+def test_get_plugin_by_name_fake_plugin(plugin_manager):\n+    \"\"\"\n+    Verify that the get_plugin_by_name function does not return values when a fake name or category is given.\n+    \"\"\"\n+    assert not plugin_manager.get_plugin_by_name('fakename', 'no category')\n+    assert not plugin_manager.get_plugin_by_name('Audio Feedback', 'fake category')\n+    assert not plugin_manager.get_plugin_by_name('fakename', 'Output')\n \n-    def test_audio_mixer_bin(self):\n-        '''Check that audio mixer plugins are returning a gst.Bin object\n \n-        Verifies that get_audioinput_bin() is returning the proper object.\n-        '''\n-        plugins = self.plugman.get_plugins_of_category(\"AudioMixer\")\n+@pytest.mark.parametrize('platform', ['linux2', 'win32', 'darwin', 'fake-dos'])\n+def test_get_all_plugins(plugin_manager, monkeypatch, plugin_platform_category_cache, platform):\n+    \"\"\"\n+    Verifies that get_all_plugins() finds the correct number of plugins for each of the specified\n+    platforms. Verifies that 0 plugins are found for a platform that does not exist.\n+    \"\"\"\n+    monkeypatch.setattr(sys, 'platform', platform)\n+    plugins = plugin_manager.get_all_plugins()\n+    assert len(plugins) == sum(plugin_platform_category_cache[platform].values())\n \n-        for plugin in plugins:\n-            plugin.plugin_object.load_config(self.plugman)\n-            plugin_bin = plugin.plugin_object.get_audiomixer_bin()\n-            self.assertIsInstance(plugin_bin, gst.Bin,\n-                \"%s did not return a gst.Bin object\" % plugin.name)\n \n-    def test_video_input_bin(self):\n-        '''Check that video input plugins are returning a gst.Bin object\n+@pytest.mark.parametrize('platform', ['win32', 'darwin', 'linux', 'linux2', 'fake-dos'])\n+def test_get_plugins_of_category(plugin_manager, plugin_platform_category_cache, monkeypatch, platform):\n+    \"\"\"\n+    Assert that fetching plugins by categories works for multiple platforms.\n \n-        Verifies that get_videoinput_bin() is returning the proper object.\n-        '''\n-        plugins = self.plugman.get_plugins_of_category(\"VideoInput\")\n+    This assertion is checked by counting the number of platform compatible plugins.\n+    \"\"\"\n \n-        for plugin in plugins:\n-            plugin.plugin_object.load_config(self.plugman)\n-            if plugin.name == \"Firewire Source\":\n-                # There is an issue with Firewire Source in testing\n-                # Skip until this is resolved\n-                continue\n+    monkeypatch.setattr(sys, 'platform', platform)\n+    audioinput_count = plugin_platform_category_cache[platform]['audioinput']\n+    audiomixer_count = plugin_platform_category_cache[platform]['audiomixer']\n+    videoinput_count = plugin_platform_category_cache[platform]['videoinput']\n+    videomixer_count = plugin_platform_category_cache[platform]['videomixer']\n+    importer_count = plugin_platform_category_cache[platform]['importer']\n+    output_count = plugin_platform_category_cache[platform]['output']\n \n-            plugin_bin = plugin.plugin_object.get_videoinput_bin()\n-            self.assertIsInstance(plugin_bin, gst.Bin,\n-                \"%s did not return a gst.Bin object\" % plugin.name)\n+    # Categories: AudioInput, AudioMixer, VideoInput, VideoMixer, Importer, Output\n+    assert len(plugin_manager.get_plugins_of_category('AudioInput')) == audioinput_count\n+    assert len(plugin_manager.get_plugins_of_category('AudioMixer')) == audiomixer_count\n+    assert len(plugin_manager.get_plugins_of_category('VideoInput')) == videoinput_count\n+    assert len(plugin_manager.get_plugins_of_category('VideoMixer')) == videomixer_count\n+    assert len(plugin_manager.get_plugins_of_category('Importer')) == importer_count\n+    assert len(plugin_manager.get_plugins_of_category('Output')) == output_count\n \n-    def test_video_mixer_bin(self):\n-        '''Check that video mixer plugins are returning a gst.Bin object\n \n-        Verifies that get_videomixer_bin() is returning the proper object.\n-        '''\n-        plugins = self.plugman.get_plugins_of_category(\"VideoMixer\")\n+@pytest.mark.parametrize('platform', ['win32', 'darwin', 'linux', 'linux2', 'fake-dos'])\n+def test_get_category_plugins(plugin_manager, plugin_platform_category_cache, monkeypatch, platform):\n+    \"\"\"This test asserts that plugin getting commands work on multiple platforms.\"\"\"\n+    monkeypatch.setattr(sys, 'platform', platform)\n+    audioinput_count = plugin_platform_category_cache[platform]['audioinput']\n+    audiomixer_count = plugin_platform_category_cache[platform]['audiomixer']\n+    videoinput_count = plugin_platform_category_cache[platform]['videoinput']\n+    videomixer_count = plugin_platform_category_cache[platform]['videomixer']\n+    importer_count = plugin_platform_category_cache[platform]['importer']\n+    output_count = plugin_platform_category_cache[platform]['output']\n \n-        for plugin in plugins:\n-            plugin.plugin_object.load_config(self.plugman)\n-            plugin_bin = plugin.plugin_object.get_videomixer_bin()\n-            self.assertIsInstance(plugin_bin, gst.Bin,\n-                \"%s did not return a gst.Bin object\" % plugin.name)\n+    assert len(plugin_manager.get_audioinput_plugins()) == audioinput_count\n+    assert len(plugin_manager.get_audiomixer_plugins()) == audiomixer_count\n+    assert len(plugin_manager.get_videoinput_plugins()) == videoinput_count\n+    assert len(plugin_manager.get_videomixer_plugins()) == videomixer_count\n+    assert len(plugin_manager.get_importer_plugins()) == importer_count\n+    assert len(plugin_manager.get_output_plugins()) == output_count\n \n-    def test_output_bin(self):\n-        '''Check that output plugins are returning a gst.Bin object\n \n-        Verifies that get_output_bin() is returning the proper object.\n-        '''\n-        plugins = self.plugman.get_plugins_of_category(\"Output\")\n+def test_load_plugin_config(plugin_manager):\n+    \"\"\"\n+    Test that if a plugin sets a CONFIG_CLASS attribute then the configuration can be loaded.\n+    \"\"\"\n+    plugins = plugin_manager.get_all_plugins()\n+    for plugin in plugins:\n+        if hasattr(plugin.plugin_object, 'config'):\n+            assert plugin.plugin_object.CONFIG_CLASS\n+            assert not plugin.config","path":"src/freeseer/tests/framework/test_plugins.py","position":299,"original_position":299,"commit_id":"3fbdda38c1d0dbc27852353b5a8bf11f56ad02f8","original_commit_id":"3fbdda38c1d0dbc27852353b5a8bf11f56ad02f8","user":{"login":"zxiiro","id":150177,"avatar_url":"https://avatars.githubusercontent.com/u/150177?v=3","gravatar_id":"","url":"https://api.github.com/users/zxiiro","html_url":"https://github.com/zxiiro","followers_url":"https://api.github.com/users/zxiiro/followers","following_url":"https://api.github.com/users/zxiiro/following{/other_user}","gists_url":"https://api.github.com/users/zxiiro/gists{/gist_id}","starred_url":"https://api.github.com/users/zxiiro/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zxiiro/subscriptions","organizations_url":"https://api.github.com/users/zxiiro/orgs","repos_url":"https://api.github.com/users/zxiiro/repos","events_url":"https://api.github.com/users/zxiiro/events{/privacy}","received_events_url":"https://api.github.com/users/zxiiro/received_events","type":"User","site_admin":false},"body":"That's because the backend loads configuration when it needs it. See:\r\n\r\nhttps://github.com/Freeseer/freeseer/blob/master/src/freeseer/framework/multimedia.py","created_at":"2014-11-18T21:41:18Z","updated_at":"2014-11-18T21:41:18Z","html_url":"https://github.com/Freeseer/freeseer/pull/642#discussion_r20540558","pull_request_url":"https://api.github.com/repos/Freeseer/freeseer/pulls/642","_links":{"self":{"href":"https://api.github.com/repos/Freeseer/freeseer/pulls/comments/20540558"},"html":{"href":"https://github.com/Freeseer/freeseer/pull/642#discussion_r20540558"},"pull_request":{"href":"https://api.github.com/repos/Freeseer/freeseer/pulls/642"}}},"pull_request":{"url":"https://api.github.com/repos/Freeseer/freeseer/pulls/642","id":23726130,"html_url":"https://github.com/Freeseer/freeseer/pull/642","diff_url":"https://github.com/Freeseer/freeseer/pull/642.diff","patch_url":"https://github.com/Freeseer/freeseer/pull/642.patch","issue_url":"https://api.github.com/repos/Freeseer/freeseer/issues/642","number":642,"state":"open","locked":false,"title":"Refactor and add tests for plugins.py","user":{"login":"wigglier","id":8321642,"avatar_url":"https://avatars.githubusercontent.com/u/8321642?v=3","gravatar_id":"","url":"https://api.github.com/users/wigglier","html_url":"https://github.com/wigglier","followers_url":"https://api.github.com/users/wigglier/followers","following_url":"https://api.github.com/users/wigglier/following{/other_user}","gists_url":"https://api.github.com/users/wigglier/gists{/gist_id}","starred_url":"https://api.github.com/users/wigglier/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wigglier/subscriptions","organizations_url":"https://api.github.com/users/wigglier/orgs","repos_url":"https://api.github.com/users/wigglier/repos","events_url":"https://api.github.com/users/wigglier/events{/privacy}","received_events_url":"https://api.github.com/users/wigglier/received_events","type":"User","site_admin":false},"body":"Improvement to the tests currently being run on ``plugins.py``\r\n\r\nrelated #644 ","created_at":"2014-11-02T00:16:07Z","updated_at":"2014-11-18T21:41:18Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5497e2542b7a62c883eb37a2d54cd096bcbb544c","assignee":null,"milestone":{"url":"https://api.github.com/repos/Freeseer/freeseer/milestones/17","labels_url":"https://api.github.com/repos/Freeseer/freeseer/milestones/17/labels","id":563652,"number":17,"title":"2014 Fall - UCOSP","description":"UCOSP Projects for 2014 Fall term.","creator":{"login":"zxiiro","id":150177,"avatar_url":"https://avatars.githubusercontent.com/u/150177?v=3","gravatar_id":"","url":"https://api.github.com/users/zxiiro","html_url":"https://github.com/zxiiro","followers_url":"https://api.github.com/users/zxiiro/followers","following_url":"https://api.github.com/users/zxiiro/following{/other_user}","gists_url":"https://api.github.com/users/zxiiro/gists{/gist_id}","starred_url":"https://api.github.com/users/zxiiro/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zxiiro/subscriptions","organizations_url":"https://api.github.com/users/zxiiro/orgs","repos_url":"https://api.github.com/users/zxiiro/repos","events_url":"https://api.github.com/users/zxiiro/events{/privacy}","received_events_url":"https://api.github.com/users/zxiiro/received_events","type":"User","site_admin":false},"open_issues":18,"closed_issues":41,"state":"open","created_at":"2014-02-09T17:41:03Z","updated_at":"2014-11-18T00:15:50Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/Freeseer/freeseer/pulls/642/commits","review_comments_url":"https://api.github.com/repos/Freeseer/freeseer/pulls/642/comments","review_comment_url":"https://api.github.com/repos/Freeseer/freeseer/pulls/comments/{number}","comments_url":"https://api.github.com/repos/Freeseer/freeseer/issues/642/comments","statuses_url":"https://api.github.com/repos/Freeseer/freeseer/statuses/3fbdda38c1d0dbc27852353b5a8bf11f56ad02f8","head":{"label":"wigglier:484-plugin-test-refactor","ref":"484-plugin-test-refactor","sha":"3fbdda38c1d0dbc27852353b5a8bf11f56ad02f8","user":{"login":"wigglier","id":8321642,"avatar_url":"https://avatars.githubusercontent.com/u/8321642?v=3","gravatar_id":"","url":"https://api.github.com/users/wigglier","html_url":"https://github.com/wigglier","followers_url":"https://api.github.com/users/wigglier/followers","following_url":"https://api.github.com/users/wigglier/following{/other_user}","gists_url":"https://api.github.com/users/wigglier/gists{/gist_id}","starred_url":"https://api.github.com/users/wigglier/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wigglier/subscriptions","organizations_url":"https://api.github.com/users/wigglier/orgs","repos_url":"https://api.github.com/users/wigglier/repos","events_url":"https://api.github.com/users/wigglier/events{/privacy}","received_events_url":"https://api.github.com/users/wigglier/received_events","type":"User","site_admin":false},"repo":{"id":23132318,"name":"freeseer","full_name":"wigglier/freeseer","owner":{"login":"wigglier","id":8321642,"avatar_url":"https://avatars.githubusercontent.com/u/8321642?v=3","gravatar_id":"","url":"https://api.github.com/users/wigglier","html_url":"https://github.com/wigglier","followers_url":"https://api.github.com/users/wigglier/followers","following_url":"https://api.github.com/users/wigglier/following{/other_user}","gists_url":"https://api.github.com/users/wigglier/gists{/gist_id}","starred_url":"https://api.github.com/users/wigglier/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wigglier/subscriptions","organizations_url":"https://api.github.com/users/wigglier/orgs","repos_url":"https://api.github.com/users/wigglier/repos","events_url":"https://api.github.com/users/wigglier/events{/privacy}","received_events_url":"https://api.github.com/users/wigglier/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/wigglier/freeseer","description":"A set of tools designed for capturing presentations. The main tool is capable of capturing VGA output and audio, and mixing them together to produce a video.","fork":true,"url":"https://api.github.com/repos/wigglier/freeseer","forks_url":"https://api.github.com/repos/wigglier/freeseer/forks","keys_url":"https://api.github.com/repos/wigglier/freeseer/keys{/key_id}","collaborators_url":"https://api.github.com/repos/wigglier/freeseer/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/wigglier/freeseer/teams","hooks_url":"https://api.github.com/repos/wigglier/freeseer/hooks","issue_events_url":"https://api.github.com/repos/wigglier/freeseer/issues/events{/number}","events_url":"https://api.github.com/repos/wigglier/freeseer/events","assignees_url":"https://api.github.com/repos/wigglier/freeseer/assignees{/user}","branches_url":"https://api.github.com/repos/wigglier/freeseer/branches{/branch}","tags_url":"https://api.github.com/repos/wigglier/freeseer/tags","blobs_url":"https://api.github.com/repos/wigglier/freeseer/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/wigglier/freeseer/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/wigglier/freeseer/git/refs{/sha}","trees_url":"https://api.github.com/repos/wigglier/freeseer/git/trees{/sha}","statuses_url":"https://api.github.com/repos/wigglier/freeseer/statuses/{sha}","languages_url":"https://api.github.com/repos/wigglier/freeseer/languages","stargazers_url":"https://api.github.com/repos/wigglier/freeseer/stargazers","contributors_url":"https://api.github.com/repos/wigglier/freeseer/contributors","subscribers_url":"https://api.github.com/repos/wigglier/freeseer/subscribers","subscription_url":"https://api.github.com/repos/wigglier/freeseer/subscription","commits_url":"https://api.github.com/repos/wigglier/freeseer/commits{/sha}","git_commits_url":"https://api.github.com/repos/wigglier/freeseer/git/commits{/sha}","comments_url":"https://api.github.com/repos/wigglier/freeseer/comments{/number}","issue_comment_url":"https://api.github.com/repos/wigglier/freeseer/issues/comments/{number}","contents_url":"https://api.github.com/repos/wigglier/freeseer/contents/{+path}","compare_url":"https://api.github.com/repos/wigglier/freeseer/compare/{base}...{head}","merges_url":"https://api.github.com/repos/wigglier/freeseer/merges","archive_url":"https://api.github.com/repos/wigglier/freeseer/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/wigglier/freeseer/downloads","issues_url":"https://api.github.com/repos/wigglier/freeseer/issues{/number}","pulls_url":"https://api.github.com/repos/wigglier/freeseer/pulls{/number}","milestones_url":"https://api.github.com/repos/wigglier/freeseer/milestones{/number}","notifications_url":"https://api.github.com/repos/wigglier/freeseer/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/wigglier/freeseer/labels{/name}","releases_url":"https://api.github.com/repos/wigglier/freeseer/releases{/id}","created_at":"2014-08-20T01:54:43Z","updated_at":"2014-09-20T20:12:08Z","pushed_at":"2014-11-17T06:00:57Z","git_url":"git://github.com/wigglier/freeseer.git","ssh_url":"git@github.com:wigglier/freeseer.git","clone_url":"https://github.com/wigglier/freeseer.git","svn_url":"https://github.com/wigglier/freeseer","homepage":"http://freeseer.github.io","size":118654,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"Freeseer:master","ref":"master","sha":"19c85be57687a11aa70d55469bf57766e7b26d79","user":{"login":"Freeseer","id":469374,"avatar_url":"https://avatars.githubusercontent.com/u/469374?v=3","gravatar_id":"","url":"https://api.github.com/users/Freeseer","html_url":"https://github.com/Freeseer","followers_url":"https://api.github.com/users/Freeseer/followers","following_url":"https://api.github.com/users/Freeseer/following{/other_user}","gists_url":"https://api.github.com/users/Freeseer/gists{/gist_id}","starred_url":"https://api.github.com/users/Freeseer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Freeseer/subscriptions","organizations_url":"https://api.github.com/users/Freeseer/orgs","repos_url":"https://api.github.com/users/Freeseer/repos","events_url":"https://api.github.com/users/Freeseer/events{/privacy}","received_events_url":"https://api.github.com/users/Freeseer/received_events","type":"Organization","site_admin":false},"repo":{"id":391515,"name":"freeseer","full_name":"Freeseer/freeseer","owner":{"login":"Freeseer","id":469374,"avatar_url":"https://avatars.githubusercontent.com/u/469374?v=3","gravatar_id":"","url":"https://api.github.com/users/Freeseer","html_url":"https://github.com/Freeseer","followers_url":"https://api.github.com/users/Freeseer/followers","following_url":"https://api.github.com/users/Freeseer/following{/other_user}","gists_url":"https://api.github.com/users/Freeseer/gists{/gist_id}","starred_url":"https://api.github.com/users/Freeseer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Freeseer/subscriptions","organizations_url":"https://api.github.com/users/Freeseer/orgs","repos_url":"https://api.github.com/users/Freeseer/repos","events_url":"https://api.github.com/users/Freeseer/events{/privacy}","received_events_url":"https://api.github.com/users/Freeseer/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Freeseer/freeseer","description":"Designed for capturing presentations at conferences. Pre-fill a list of talks to record, record them, and upload them to YouTube with our YouTube Uploader.","fork":false,"url":"https://api.github.com/repos/Freeseer/freeseer","forks_url":"https://api.github.com/repos/Freeseer/freeseer/forks","keys_url":"https://api.github.com/repos/Freeseer/freeseer/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Freeseer/freeseer/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Freeseer/freeseer/teams","hooks_url":"https://api.github.com/repos/Freeseer/freeseer/hooks","issue_events_url":"https://api.github.com/repos/Freeseer/freeseer/issues/events{/number}","events_url":"https://api.github.com/repos/Freeseer/freeseer/events","assignees_url":"https://api.github.com/repos/Freeseer/freeseer/assignees{/user}","branches_url":"https://api.github.com/repos/Freeseer/freeseer/branches{/branch}","tags_url":"https://api.github.com/repos/Freeseer/freeseer/tags","blobs_url":"https://api.github.com/repos/Freeseer/freeseer/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Freeseer/freeseer/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Freeseer/freeseer/git/refs{/sha}","trees_url":"https://api.github.com/repos/Freeseer/freeseer/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Freeseer/freeseer/statuses/{sha}","languages_url":"https://api.github.com/repos/Freeseer/freeseer/languages","stargazers_url":"https://api.github.com/repos/Freeseer/freeseer/stargazers","contributors_url":"https://api.github.com/repos/Freeseer/freeseer/contributors","subscribers_url":"https://api.github.com/repos/Freeseer/freeseer/subscribers","subscription_url":"https://api.github.com/repos/Freeseer/freeseer/subscription","commits_url":"https://api.github.com/repos/Freeseer/freeseer/commits{/sha}","git_commits_url":"https://api.github.com/repos/Freeseer/freeseer/git/commits{/sha}","comments_url":"https://api.github.com/repos/Freeseer/freeseer/comments{/number}","issue_comment_url":"https://api.github.com/repos/Freeseer/freeseer/issues/comments/{number}","contents_url":"https://api.github.com/repos/Freeseer/freeseer/contents/{+path}","compare_url":"https://api.github.com/repos/Freeseer/freeseer/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Freeseer/freeseer/merges","archive_url":"https://api.github.com/repos/Freeseer/freeseer/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Freeseer/freeseer/downloads","issues_url":"https://api.github.com/repos/Freeseer/freeseer/issues{/number}","pulls_url":"https://api.github.com/repos/Freeseer/freeseer/pulls{/number}","milestones_url":"https://api.github.com/repos/Freeseer/freeseer/milestones{/number}","notifications_url":"https://api.github.com/repos/Freeseer/freeseer/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Freeseer/freeseer/labels{/name}","releases_url":"https://api.github.com/repos/Freeseer/freeseer/releases{/id}","created_at":"2009-12-01T14:17:15Z","updated_at":"2014-11-18T00:15:49Z","pushed_at":"2014-11-18T00:15:49Z","git_url":"git://github.com/Freeseer/freeseer.git","ssh_url":"git@github.com:Freeseer/freeseer.git","clone_url":"https://github.com/Freeseer/freeseer.git","svn_url":"https://github.com/Freeseer/freeseer","homepage":"http://freeseer.readthedocs.org","size":127871,"stargazers_count":173,"watchers_count":173,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":95,"mirror_url":null,"open_issues_count":141,"forks":95,"open_issues":141,"watchers":173,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Freeseer/freeseer/pulls/642"},"html":{"href":"https://github.com/Freeseer/freeseer/pull/642"},"issue":{"href":"https://api.github.com/repos/Freeseer/freeseer/issues/642"},"comments":{"href":"https://api.github.com/repos/Freeseer/freeseer/issues/642/comments"},"review_comments":{"href":"https://api.github.com/repos/Freeseer/freeseer/pulls/642/comments"},"review_comment":{"href":"https://api.github.com/repos/Freeseer/freeseer/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/Freeseer/freeseer/pulls/642/commits"},"statuses":{"href":"https://api.github.com/repos/Freeseer/freeseer/statuses/3fbdda38c1d0dbc27852353b5a8bf11f56ad02f8"}}}}