Explorar el Código

Add stream decryption options to recorder

Fixes #4
Gil Pedersen hace 10 años
padre
commit
53854c5c3d
Se han modificado 3 ficheros con 60 adiciones y 31 borrados
  1. 19 4
      bin/hlsrecord
  2. 38 21
      lib/recorder.js
  3. 3 6
      lib/segment-decrypt.js

+ 19 - 4
bin/hlsrecord

@@ -7,7 +7,12 @@
 
 var fs = require('fs'),
     path = require('path');
-var nopt = require('noptify/node_modules/nopt');
+var nopt;
+try {
+  nopt = require('noptify/node_modules/nopt');
+} catch (e) {
+  nopt = require('nopt');
+}
 
 var HlsSegmentReader = require('hls-segment-reader');
 var recorder = require('../lib/recorder');
@@ -29,7 +34,6 @@ nopt.typeDefs[DateValue] = { type: DateValue, validate: function (data, key, val
 
 function HexValue(){}
 nopt.typeDefs[HexValue] = { type: HexValue, validate: function (data, key, val) {
-  console.log('!', data, key, val)
   data[key] = new Buffer(val, 'hex');
 }};
 
@@ -44,7 +48,10 @@ hlsrecord.version(require('../package').version)
    .option('create-dir', '-c', 'Explicitly create output dir', Boolean)
    .option('extension', 'Preserve specified vendor extension', Array)
    .option('segment-extension', 'Preserve specified vendor segment extension', Array)
-//   .option('-a, --user-agent <string>', 'HTTP User-Agent')
+   .option('user-agent', '-a', 'HTTP User-Agent', String)
+   .option('decrypt', 'Attempt to decrypt segments', Boolean)
+   .option('cookie', 'Add cookie header to key requests', String)
+   .option('key', 'Use oob hex encoded key to decrypt segments', HexValue)
 //   .option('-f, --full', 'record all variants')
    .parse(process.argv);
 
@@ -90,5 +97,13 @@ function createReader(src) {
   return r;
 }
 
+var decrypt = null;
+if (options.decrypt) {
+  decrypt = {
+    cookie: options.cookie,
+    key: options.key
+  };
+}
+
 var rdr = createReader(src);
-recorder(rdr, outDir, { subreader:createReader, startOffset: options['start-offset'], collect: options.collect }).start();
+recorder(rdr, outDir, { subreader:createReader, startOffset: options['start-offset'], collect: options.collect, decrypt: decrypt }).start();

+ 38 - 21
lib/recorder.js

@@ -15,10 +15,13 @@ var mime = require('mime-types'),
     writeFileAtomic = require('write-file-atomic'),
     debug = require('debug')('hls:recorder');
 
+var SegmentDecrypt = require('./segment-decrypt');
+
 // add custom extensions
 mime.extensions['audio/aac'] = ['aac'];
 mime.extensions['audio/ac3'] = ['ac3'];
 
+
 function HlsStreamRecorder(reader, dst, options) {
   options = options || {};
 
@@ -32,6 +35,7 @@ function HlsStreamRecorder(reader, dst, options) {
   this.startOffset = parseFloat(options.startOffset);
   this.subreader = options.subreader;
   this.collect = !!options.collect; // collect into a single file (v4 feature)
+  this.decrypt = options.decrypt;
 
   this.recorders = [];
 }
@@ -97,7 +101,7 @@ HlsStreamRecorder.prototype.updateIndex = function(update) {
           var rec = this.recorderForUrl(programUrl);
           if (!rec || !rec.localUrl) {
             var dir = self.variantName(program.info, index);
-            rec = new HlsStreamRecorder(self.subreader(programUrl), path.join(self.dst, dir), { startOffset: self.startOffset, collect: self.collect });
+            rec = new HlsStreamRecorder(self.subreader(programUrl), path.join(self.dst, dir), { startOffset: self.startOffset, collect: self.collect, decrypt: this.decrypt });
             rec.localUrl = url.format({pathname: path.join(dir, 'index.m3u8')});
             rec.remoteUrl = programUrl;
 
@@ -121,7 +125,7 @@ HlsStreamRecorder.prototype.updateIndex = function(update) {
             var rec = this.recorderForUrl(itemUrl);
             if (!rec || !rec.localUrl) {
               var dir = self.groupSrcName(groupItem, index);
-              rec = new HlsStreamRecorder(self.subreader(itemUrl), path.join(self.dst, dir), { startOffset: self.startOffset, collect: self.collect });
+              rec = new HlsStreamRecorder(self.subreader(itemUrl), path.join(self.dst, dir), { startOffset: self.startOffset, collect: self.collect, decrypt: this.decrypt });
               rec.localUrl = url.format({pathname: path.join(dir, 'index.m3u8')});
               rec.remoteUrl = itemUrl;
 
@@ -152,6 +156,10 @@ HlsStreamRecorder.prototype.updateIndex = function(update) {
         debug('done');
       });
     });
+
+    if (this.decrypt) {
+      this.decrypt.base = this.reader.baseUrl;
+    }
   }
 
   // validate update
@@ -159,7 +167,7 @@ HlsStreamRecorder.prototype.updateIndex = function(update) {
     throw new Error('Invalid index');
 };
 
-HlsStreamRecorder.prototype.process = function(segmentInfo, next) {
+HlsStreamRecorder.prototype.process = function(segmentInfo, done) {
   var self = this;
 
   var segment = new m3u8parse.M3U8Segment(segmentInfo.details, true);
@@ -187,29 +195,38 @@ HlsStreamRecorder.prototype.process = function(segmentInfo, next) {
   }
 
   // save the stream segment
-  var stream = oncemore(segmentInfo.stream);
-  stream.pipe(fs.createWriteStream(path.join(this.dst, segment.uri), { flags: newFile ? 'w' : 'a' }));
+  SegmentDecrypt.decrypt(segmentInfo.stream, segmentInfo.details.key, this.decrypt, function (err, stream) {
+    if (err) {
+      console.error('decrypt failed', err.stack);
+      stream = segmentInfo.stream;
+    } else {
+      segment.key = null;
+    }
 
-  var bytesWritten = 0;
-  if (this.collect) {
-    stream.on('data', function(chunk) {
-      bytesWritten += chunk.length;
-    });
-  }
+    stream = oncemore(stream);
+    stream.pipe(fs.createWriteStream(path.join(self.dst, segment.uri), { flags: newFile ? 'w' : 'a' }));
 
-  stream.once('end', 'error', function(err) {
-    // only to report errors
-    if (err) debug('stream error', err.stack || err);
+    var bytesWritten = 0;
+    if (self.collect) {
+      stream.on('data', function(chunk) {
+        bytesWritten += chunk.length;
+      });
+    }
 
-    if (segment.byterange)
-      segment.byterange.length = bytesWritten;
+    stream.once('end', 'error', function(err) {
+      // only to report errors
+      if (err) debug('stream error', err.stack || err);
 
-    // update index
-    self.index.segments.push(segment);
-    self.flushIndex(next);
-  });
+      if (segment.byterange)
+        segment.byterange.length = bytesWritten;
 
-  this.seq++;
+      // update index
+      self.index.segments.push(segment);
+      self.flushIndex(done);
+    });
+
+    self.seq++;
+  });
 };
 
 HlsStreamRecorder.prototype.variantName = function(info, index) {

+ 3 - 6
lib/segment-decrypt.js

@@ -55,10 +55,7 @@ internals.KeyFetcher.prototype.get = function (next) {
     self._gets = [];
 
     for (var idx = 0; idx < gets.length; idx++) {
-      var _next = gets[idx];
-      process.nextTick(function() {
-        _next(err, key);
-      });
+      process.nextTick(gets[idx], err, key);
     }
   };
 
@@ -84,7 +81,7 @@ internals.fetchKey = function (keyUri, options, next) {
 
 exports.decrypt = function (stream, keyAttrs, options, next) {
   var method = keyAttrs && keyAttrs.enumeratedString('method');
-  if (!method || method === 'NONE') {
+  if (!options || !method || method === 'NONE') {
     return next(null, stream);
   }
 
@@ -103,7 +100,7 @@ exports.decrypt = function (stream, keyAttrs, options, next) {
     try {
       var decrypt = Crypto.createDecipheriv('aes-128-cbc', key, iv);
     } catch (ex) {
-      return next(new Error('crypto setup failed: ' (ex.stack || ex)));
+      return next(new Error('crypto setup failed: ' + (ex.stack || ex)));
     }
 
     // forward stream errors