├── .gitignore
├── .jshintrc
├── Dockerfile
├── README.md
├── app.js
├── bin
└── www
├── circle.yml
├── lib
└── extractor.js
├── package.json
├── routes
└── index.js
└── test
├── apiSpec.js
├── extractorSpec.js
└── htmls
├── arstechnica.html
├── cnet.html
├── dailymail.html
├── kickstarter.html
├── mashable.html
├── techcrunch.html
├── wired.html
└── youtube.html
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 |
3 | # Logs
4 | logs
5 | *.log
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19 | .grunt
20 |
21 | # node-waf configuration
22 | .lock-wscript
23 |
24 | # Compiled binary addons (http://nodejs.org/api/addons.html)
25 | build/Release
26 |
27 | # Dependency directory
28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
29 | node_modules
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | // JSHint Default Configuration File (as on JSHint website)
3 | // See http://jshint.com/docs/ for more details
4 |
5 | "maxerr" : 50, // {int} Maximum error before stopping
6 |
7 | // Enforcing
8 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
9 | "camelcase" : false, // true: Identifiers must be in camelCase
10 | "curly" : false, // true: Require {} for every new block or scope
11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison
12 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
13 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
14 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
15 | "indent" : 4, // {int} Number of spaces to use for indentation
16 | "latedef" : false, // true: Require variables/functions to be defined before being used
17 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
18 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
19 | "noempty" : true, // true: Prohibit use of empty blocks
20 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters.
21 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
22 | "plusplus" : false, // true: Prohibit use of `++` & `--`
23 | "quotmark" : "single", // Quotation mark consistency:
24 | // false : do nothing (default)
25 | // true : ensure whatever is used is consistent
26 | // "single" : require single quotes
27 | // "double" : require double quotes
28 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
29 | "unused" : true, // true: Require all defined variables be used
30 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode
31 | "maxparams" : false, // {int} Max number of formal params allowed per function
32 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions)
33 | "maxstatements" : false, // {int} Max number statements per function
34 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function
35 | "maxlen" : false, // {int} Max number of characters per line
36 |
37 | // Relaxing
38 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
39 | "boss" : false, // true: Tolerate assignments where comparisons would be expected
40 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
41 | "eqnull" : false, // true: Tolerate use of `== null`
42 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
43 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
44 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
45 | // (ex: `for each`, multiple try/catch, function expression…)
46 | "evil" : false, // true: Tolerate use of `eval` and `new Function()`
47 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs
48 | "funcscope" : false, // true: Tolerate defining variables inside control statements
49 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
50 | "iterator" : false, // true: Tolerate using the `__iterator__` property
51 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
52 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings
53 | "laxcomma" : false, // true: Tolerate comma-first style coding
54 | "loopfunc" : false, // true: Tolerate functions being defined in loops
55 | "multistr" : false, // true: Tolerate multi-line strings
56 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them.
57 | "notypeof" : false, // true: Tolerate invalid typeof operator values
58 | "proto" : false, // true: Tolerate using the `__proto__` property
59 | "scripturl" : false, // true: Tolerate script-targeted URLs
60 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
61 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
62 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
63 | "validthis" : false, // true: Tolerate using this in a non-constructor function
64 | "predef" : [ "-Promise" ],
65 |
66 | // Environments
67 | "browser" : true, // Web Browser (window, document, etc)
68 | "browserify" : false, // Browserify (node.js code in the browser)
69 | "couch" : false, // CouchDB
70 | "devel" : true, // Development/debugging (alert, confirm, etc)
71 | "dojo" : false, // Dojo Toolkit
72 | "jasmine" : false, // Jasmine
73 | "jquery" : false, // jQuery
74 | "mocha" : true, // Mocha
75 | "mootools" : false, // MooTools
76 | "node" : true, // Node.js
77 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
78 | "prototypejs" : false, // Prototype and Scriptaculous
79 | "qunit" : false, // QUnit
80 | "rhino" : false, // Rhino
81 | "shelljs" : false, // ShellJS
82 | "worker" : false, // Web Workers
83 | "wsh" : false, // Windows Scripting Host
84 | "yui" : false, // Yahoo User Interface
85 |
86 | // Custom Globals
87 | "globals" : {
88 | "$": true,
89 | "$$": true,
90 | "_": true,
91 | "analytics": true,
92 | "angular": true,
93 | "browser": true,
94 | "by": true,
95 | "expect": true,
96 | "swal": true
97 | }
98 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:0.12
2 |
3 | COPY . /app
4 |
5 | WORKDIR /app
6 |
7 | RUN \
8 | rm -rf node_modules .env log && \
9 | npm install
10 |
11 | ENV NODE_ENV production
12 | ENV LOG_NAME url-metadata-extractor
13 | ENV PORT 80
14 |
15 | EXPOSE 80
16 |
17 | CMD ["npm", "start"]
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | An API that extracts metadata from a given article URL. It uses a combination metatags, item properties and link tags to find metadata. Does not do any sophisticated NLP.
2 |
3 | ## Start with Docker
4 |
5 | ```
6 | docker run -d -p 3000:80 blikk/url-metadata-extractor
7 | ```
8 |
9 | ## Usage
10 |
11 | ```shell
12 | curl -XPOST http://localhost:3000/extract \
13 | --header "Content-Type:application/json" \
14 | --data '{"url": "http://techcrunch.com/2015/01/06/razer-announces-199-open-source-virtual-reality-headset/"}'
15 | ```
16 |
17 | ## Example Response
18 |
19 | ```json
20 | {
21 | "url": "http://techcrunch.com/2015/01/06/razer-announces-199-open-source-virtual-reality-headset/",
22 | "metatags": {
23 | "p:domain_verify": "ee3dd0cd95c625f8f2446d34712815ed",
24 | "handheldfriendly": "True",
25 | "mobileoptimized": "320",
26 | "viewport": "initial-scale=1.0,width=device-width,user-scalable=no,minimum-scale=1.0,maximum-scale=1.0",
27 | "apple-mobile-web-app-title": "TechCrunch",
28 | "robots": "NOYDIR,NOODP",
29 | "google-site-verification": "4U1OC1LwZlFHAehLhCV4rt3YzWI_AyF7Gb0XqlaVEhE",
30 | "msvalidate:01": "5ABD8A078F3356F3A6A8C8643C31FB8F",
31 | "generator": "WordPress.com",
32 | "sailthru:date": "2015-01-06 06:00:51",
33 | "sailthru:title": "Razer Announces $199 ‘Open Source’ Virtual Reality Headset",
34 | "sailthru:tags": "Virtual reality",
35 | "sailthru:author": "Kyle Russell (@kylebrussell ) ",
36 | "sailthru:description": "The headset, which the company says will arrive this June, is meant to serve as an alternative hardware test bed for developers looking at the VR space.",
37 | "sailthru:image.full": "https://tctechcrunch2011.files.wordpress.com/2015/01/img_0023.png",
38 | "sailthru:image.thumb": "https://tctechcrunch2011.files.wordpress.com/2015/01/img_0023.png?w=50",
39 | "fb:app_id": "187288694643718",
40 | "fb:admins": "1076790301,543710097,500024101,771265067,1661021707,1550970059,663677613,10219991,1178144075,726995222,506404657,4700188",
41 | "article:publisher": "https://www.facebook.com/techcrunch",
42 | "og:site_name": "TechCrunch",
43 | "og:site": "social.techcrunch.com",
44 | "og:title": "Razer Announces $199 ‘Open Source’ Virtual Reality Headset",
45 | "og:description": "The headset, which the company says will arrive this June, is meant to serve as an alternative hardware test bed for developers looking at the VR space.",
46 | "og:image": "https://tctechcrunch2011.files.wordpress.com/2015/01/img_0023.png?w=680",
47 | "og:url": "http://social.techcrunch.com/2015/01/06/razer-announces-199-open-source-virtual-reality-headset/",
48 | "og:type": "article",
49 | "twitter:card": "summary_large_image",
50 | "twitter:image:src": "https://tctechcrunch2011.files.wordpress.com/2015/01/img_0023.png",
51 | "twitter:site": "@techcrunch",
52 | "twitter:url": "http://techcrunch.com/2015/01/06/razer-announces-199-open-source-virtual-reality-headset/",
53 | "twitter:description": "The headset, which the company says will arrive this June, is meant to serve as an alternative hardware test bed for developers looking at the VR space.",
54 | "twitter:title": "Razer Announces $199 ‘Open Source’ Virtual Reality…",
55 | "description": "The headset, which the company says will arrive this June, is meant to serve as an alternative hardware test bed for developers looking at the VR space.",
56 | "application-name": "TechCrunch",
57 | "msapplication-window": "width=device-width;height=device-height",
58 | "msapplication-tooltip": "Startup and Technology News",
59 | "msapplication-task": "name=Subscribe;action-uri=http://techcrunch.com/feed/;icon-uri=http://0.gravatar.com/blavatar/ad5b2a2b10ddd8fc4e4588abd4e12a84?s=16"
60 | },
61 | "title": "Razer Announces $199 ‘Open Source’ Virtual Reality Headset",
62 | "description": "The headset, which the company says will arrive this June, is meant to serve as an alternative hardware test bed for developers looking at the VR space.",
63 | "image": "https://tctechcrunch2011.files.wordpress.com/2015/01/img_0023.png?w=680",
64 | "contentType": "article",
65 | "site": "TechCrunch",
66 | "date": "2015-01-06T06:00:51+00:00"
67 | }
68 | ```
69 |
70 | ## Build yourself
71 |
72 | ```
73 | npm install
74 | npm start
75 | ```
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('dotenv').load();
4 | var express = require('express');
5 | var path = require('path');
6 | var bodyParser = require('body-parser');
7 | var log = require('blikk-logjs')('app');
8 |
9 | var app = express();
10 |
11 | // view engine setup
12 | app.set('views', path.join(__dirname, 'views'));
13 | app.set('view engine', 'jade');
14 |
15 | // uncomment after placing your favicon in /public
16 | //app.use(favicon(__dirname + '/public/favicon.ico'));
17 | app.use(bodyParser.json());
18 | app.use(bodyParser.urlencoded({ extended: false }));
19 |
20 | // Bunyan logging middleware
21 | app.use(function(req, res, next){
22 | log.info({req: req});
23 | next();
24 | });
25 |
26 | app.use('/', require('./routes/index'));
27 |
28 | // catch 404 and forward to error handler
29 | app.use(function(req, res, next) {
30 | var err = new Error('Not Found');
31 | err.status = 404;
32 | next(err);
33 | });
34 |
35 | // development error handler
36 | // will print stacktrace
37 | if (app.get('env') === 'development') {
38 | app.use(function(err, req, res, next) {
39 | res.status(err.status || 500);
40 | log.error({req: req, err: err});
41 | res.send(err);
42 | });
43 | }
44 |
45 | // production error handler
46 | // no stacktraces leaked to user
47 | app.use(function(err, req, res, next) {
48 | res.status(err.status || 500);
49 | log.error({req: req, err: err});
50 | res.send(err.message);
51 | });
52 |
53 | module.exports = app;
54 |
--------------------------------------------------------------------------------
/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | var app = require('../app');
8 | var debug = require('debug')('url-metadata-extractor:server');
9 | var http = require('http');
10 |
11 | /**
12 | * Get port from environment and store in Express.
13 | */
14 |
15 | var port = normalizePort(process.env.PORT || '3000');
16 | app.set('port', port);
17 |
18 | /**
19 | * Create HTTP server.
20 | */
21 |
22 | var server = http.createServer(app);
23 |
24 | /**
25 | * Listen on provided port, on all network interfaces.
26 | */
27 |
28 | server.listen(port);
29 | server.on('error', onError);
30 | server.on('listening', onListening);
31 |
32 | /**
33 | * Normalize a port into a number, string, or false.
34 | */
35 |
36 | function normalizePort(val) {
37 | var port = parseInt(val, 10);
38 |
39 | if (isNaN(port)) {
40 | // named pipe
41 | return val;
42 | }
43 |
44 | if (port >= 0) {
45 | // port number
46 | return port;
47 | }
48 |
49 | return false;
50 | }
51 |
52 | /**
53 | * Event listener for HTTP server "error" event.
54 | */
55 |
56 | function onError(error) {
57 | if (error.syscall !== 'listen') {
58 | throw error;
59 | }
60 |
61 | var bind = typeof port === 'string'
62 | ? 'Pipe ' + port
63 | : 'Port ' + port;
64 |
65 | // handle specific listen errors with friendly messages
66 | switch (error.code) {
67 | case 'EACCES':
68 | console.error(bind + ' requires elevated privileges');
69 | process.exit(1);
70 | break;
71 | case 'EADDRINUSE':
72 | console.error(bind + ' is already in use');
73 | process.exit(1);
74 | break;
75 | default:
76 | throw error;
77 | }
78 | }
79 |
80 | /**
81 | * Event listener for HTTP server "listening" event.
82 | */
83 |
84 | function onListening() {
85 | var addr = server.address();
86 | var bind = typeof addr === 'string'
87 | ? 'pipe ' + addr
88 | : 'port ' + addr.port;
89 | debug('Listening on ' + bind);
90 | }
91 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | services:
3 | - docker
4 | environment:
5 | DOCKER_EMAIL: deploy@blikk.co
6 | DOCKER_USER: blikkdeploy
7 | LOG_NAME: url-metadata-extractor
8 | VERSION: 0.2.7
9 |
10 | dependencies:
11 | pre:
12 | - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASSWORD
13 |
14 | test:
15 | post:
16 | - docker build -t blikk/url-metadata-extractor .
17 | - docker tag blikk/url-metadata-extractor:latest blikk/url-metadata-extractor:$CIRCLE_SHA1
18 | - docker tag blikk/url-metadata-extractor:latest blikk/url-metadata-extractor:$VERSION
19 |
20 | deployment:
21 | hub:
22 | branch: master
23 | commands:
24 | - docker push blikk/url-metadata-extractor
--------------------------------------------------------------------------------
/lib/extractor.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var moment = require('moment');
4 | var _ = require('underscore');
5 | var request = require('superagent');
6 | var Promise = require('bluebird');
7 | var cheerio = require('cheerio');
8 | var entities = require("entities")
9 |
10 | var Extractor = function(){};
11 |
12 | Extractor.prototype.extract = function(url, callback) {
13 | var extractor = this;
14 | Promise.fromNode(function(callback){
15 | request.get(url).end(callback);
16 | }).then(function(res){
17 | if (res.ok) {
18 | return callback(null, extractor.getMetadataFromHtml(url, res.text));
19 | } else {
20 | return callback(new Error(res.rext), null);
21 | }
22 | }).catch(function(err){
23 | callback(err, null);
24 | });
25 | };
26 |
27 | Extractor.prototype.getMetatagsFromHtml = function(html) {
28 | var $ = cheerio.load(html);
29 | var headMetatags = $('head meta');
30 | var metadataObjects = headMetatags.map(function(){
31 | var result = {};
32 | var name = $(this).attr('name') || $(this).attr('property');
33 | var content = $(this).attr('content');
34 | if(name && content){
35 | // JSON objects aren't allowed to contain dots - flatten it
36 | name = name.replace('.', ':').toLowerCase();
37 | var value = entities.decodeHTML(content);
38 | result[name] = value;
39 | }
40 | return result;
41 | }).get();
42 | var metadataObj = _.reduce(metadataObjects, function(a,b){
43 | return _.extend(a,b);
44 | }, {});
45 | return metadataObj;
46 | };
47 |
48 | Extractor.prototype.findCanonicalUrl = function(html) {
49 | var $ = cheerio.load(html);
50 | var canonicalLink = $('link[rel=canonical]').attr('href');
51 | return canonicalLink;
52 | };
53 |
54 | Extractor.prototype.findDate = function(html) {
55 | var $ = cheerio.load(html);
56 |
57 | // E.g. Feb 13, 2015 4:01 pm UTC
58 | var dateClass = moment.utc($('.date').data('time') * 1000 || null);
59 | var dateClass = dateClass.isValid() ? dateClass.format() : undefined;
60 |
61 | // Eg April 8, 2015 9:01 PM PDT
3 |
4 |
5 |
6 |
7 |
8 |
9 | Mattel and Google turn the View-Master into a virtual reality toy | Ars Technica
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
259 |
260 |
263 |
264 |
265 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
Remember the View-Master—a 75-year-old stereoscopic viewer toy that used cardboard reels? Today Google and Mattel are reviving the device for the 21st century. Instead of showing still images, it's now a virtual reality toy. And instead of housing plastic film, the reels now serve as augmented reality markers that interact with the headset.
322 |
The device is basically a plastic version of Google Cardboard . Like Cardboard, the new View-Master needs a smartphone to work—a phone slides into the front of the device, serves as the screen, and runs all the content. The traditional orange handle on the side of the View-Master used to serve as the "next reel" button, but on the VR reboot it serves as the input button, allowing users to confirm choices they see on-screen. On Google cardboard this was accomplished with a magnet and a metal washer.
323 |
VIDEO
324 |
The View-Master disks are now "Experience Reels" and serve as markers for augmented reality. The disks don't go into the unit; they now sit on the table in front of the user. The patterns on the disks are picked up by the smartphone camera, and the smartphone shows a camera feed with a 3D model superimposed on the disk. By pointing the View-Master at the disk and pressing the confirm button, the user can "dive-in" to the content and switch to full VR.
325 |
Google cardboard isn't just the name of the Google's cardboard phone holder; it's also the name of Google's Android virtual reality development platform, which the new View-Master is compatible with. View-Master users can download Cardboard apps from the Play Store and use the device the same way they would a Cardboard viewer. Mattel and Google haven't talked about phone compatibility, but the current Cardboard platform is Android-powered and won't work with an iPhone.
326 |
Since the View-Master is basically a plastic phone holder, the device is only $29.99. Experience Reels will be sold in three-packs for $14.99. The device and reels are scheduled to launch this fall.
327 |
328 |
331 |
332 |
333 |
334 |
335 |
393 |
394 |
431 |
432 |
433 |
434 |
435 | Ron Amadeo / Ron is the Reviews Editor at Ars Technica, where he specializes in Android OS and Google products. He is always on the hunt for a new gadget and loves to rip things apart to see how they work.
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
451 |
452 |
453 | You May Also Like
454 |
455 |
466 |
467 |
478 |
479 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
509 |
510 |
511 |
512 | Latest Feature Story
513 |
640 |
641 |
642 |
643 |
644 |
645 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
788 |
789 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
848 |
849 |
850 |
851 |
872 |
873 |
874 |
875 |
876 |
877 |
878 |
--------------------------------------------------------------------------------
/test/htmls/mashable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
28 | Magic Leap shows off gaming on its holographic headset
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
70 |
71 |
72 | Mashable
73 |
74 |
75 |
76 |
83 |
84 |
85 |
155 |
156 |
396 |
397 |
398 |
399 |
--------------------------------------------------------------------------------
/test/htmls/techcrunch.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Razer Announces $199 ‘Open Source’ Virtual Reality Headset | TechCrunch
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
40 |
45 |
46 |
56 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
109 |
110 |
111 |
112 |
131 |
132 |
313 |
314 |
333 |
334 |
335 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
508 |
509 |
510 |
511 |
512 |
514 |
515 |
516 |
517 |
518 | 0
519 | SHARES
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 | Next Story
575 |
576 |
Fitbit Finally Starts Shipping The Charge HR And Surge Smartwatch
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
Razer , the billion-dollar gaming PC and peripheral maker , has announced a big move into the virtual reality space with the announcement of OSVR Hacker Dev Kit here at CES.
594 |
The headset, which the company says will arrive this June, is meant to serve as an alternative hardware test bed for developers looking at the VR space.
595 |
It’s also just one component of what Razer is calling its Open Source Virtual Reality ecosystem. In addition to making the headset for testing on, Razer has partnered with other players in the VR space — game engine developers like Unity, motion controller makers like Sixense and Leap Motion — to create a “standardized interface” for building virtual reality games and applications.
615 |
It’ll even work with the latest publicly available Oculus dev kit, so taking advantage of OSVR won’t prevent you from building for the leader of the VR pack.
616 |
617 |
618 |
619 | Razer OSVR Headset 01
620 |
621 |
622 |
623 |
624 | Razer OSVR Headset
625 |
626 |
627 |
628 |
629 | Razer OSVR Headset Lenses
630 |
631 |
632 |
633 |
636 |
641 |
642 |
While the $199 price is pretty sweet compared to the $350 Oculus Rift dev kit, there are a few things to consider when looking at these early this summer. It’s going to sport a 1080p resolution, comparable to the Oculus Rift DK2, but will refresh at 60 frames per second (same as the Samsung Gear VR ) rather than the DK2’s 75. That’s not a deal-breaker for most, but those predisposed to experience nausea in VR might want to hold off for something closer to the 90 frames per second ideal.
643 |
That issue might not matter in comparison to its other key features. For instance, those looking to modify the headset itself will be happy to know that it allows you to swap out the standard display for upgrades by simply removing five screws on the bottom of the headset. The upgradability doesn’t end there either — there’s also a few external USB ports on the headset for plugging in things like hand-motion trackers.
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 | 0
654 | SHARES
655 |
656 |
657 | 0
658 | Share
659 |
660 |
661 |
662 |
663 |
664 |
665 | 0
666 | Share
667 |
668 |
669 | 0
670 |
671 |
672 |
673 | 0
674 |
675 |
676 |
677 | 0
678 |
679 |
680 |
681 | 0
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
696 |
697 |
698 |
699 |
700 |
701 |
913 |
914 |
915 |
916 |
917 |
918 |
919 |
920 |
921 |
922 |
923 |
953 |
954 |
955 |
956 |
957 |
958 |
959 |
960 |
961 |
962 |
963 |
964 |
978 |
979 |
980 |
981 |
1038 |
1039 |
1040 |
1041 |
1042 |
1043 |
1044 |
1045 |
1046 |
1047 |
1048 |
1055 |
1056 |
1057 |
1190 |
1191 |
1194 |
1197 |
1198 |
1199 |
1200 |
1205 |
1206 |
1207 |
1226 |
1227 |
1228 |
1229 |
1230 |
1231 |
1236 |
1241 |
1248 |
1253 |
1254 |
1255 |
1273 |
1282 |
1283 |
1322 |
1323 |
1324 |
1325 |
--------------------------------------------------------------------------------
Promoted Comments
338 |339 |-
340 |
341 |
342 | kumquatWise, Aged Ars Veteran
343 |
344 |
345 |
346 |
347 | jump to post
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
370 |
371 |
372 |
373 |
374 | I'm torn between thinking that's awesome and giving a "Back in my day" rant.
375 |
376 |
377 |
381 |
382 |You must login or create an account to comment.
391 |