<commit_before>"use strict"

/* ferrit.js - Reads recent tweets for users or hashtags and generates ferrit
index pages with multiple cards and a ferrit card page for each tweet. */

const Twit = require('twit')
const alphanumtwid = require('alphanumeric-twitter-id')
const child_process = require('child_process')
const qrcode = require('qrcode')
const config = require('config')
const fs = require('fs-extra')
const handlebars = require('handlebars')
const moment = require('moment')
const _ = require('lodash')
const async = require('async')

const uncaught = require('uncaught');

uncaught.start();
uncaught.addListener(function (error) {
    console.log('Uncaught error or rejection: ', error.message);
});

let my_url = config.get('general.my_url')
let my_twitter = config.get('general.my_twitter')
let my_logo = config.get('general.my_logo')

/* Sets up twitter authentiction from the config file. Put it in production.toml
in twitter section and don't check that file into git. */
const T = new Twit( config.get('twitter') );

/* Fun time happy globals! */
//let screen_names =['dril']
//let hashtags = ['ferrets']
let count = 2
let screen_names = config.get('ferrits.screen_names')
let hashtags = config.get('ferrits.hashtags')
//let count = config.get('ferrits.count')

let best_user_tweets = {}
let best_hashtag_tweets = {}

/* This is bloody awful. I'm still figuring out how async works in node */
async function main() {
  Promise.all([
      generate_user_pages(screen_names, hashtags),
      generate_hashtag_pages(screen_names, hashtags)
  ]).then(function(results) {
    make_hot_index(results, screen_names, hashtags)
  })
}

async function make_hot_index(best_tweets, screen_names, hashtags) {
  let all_best_tweets = _.assignIn(best_tweets[0], best_tweets[1])
  all_best_tweets = _.pickBy(all_best_tweets, _.identity)
  let html = await gen_index_page('index_hot', all_best_tweets, 'hot', screen_names, hashtags)
  writepage('', 'index', html)
  fs.copySync('assets/', 'public/assets/')
  console.log("Finished!")
}

async function generate_user_pages (screen_names, hashtags) {
  for ( let screen_name of screen_names ) {
    try {
      const statuses = await get_statuses('statuses/user_timeline', { screen_name: screen_name, count: count })
      const fcodes = await get_fcodes(statuses)
      await ferrify_tweets(statuses, screen_name, false, screen_names, hashtags)
      let indexhtml = await gen_index_page('index_user', fcodes, screen_name, screen_names, hashtags)
      writepage(screen_name + '/', 'index', indexhtml)
      let best_tweet = await get_best_tweet(statuses)
      if (best_tweet) {
        best_user_tweets[best_tweet[0]] = best_tweet[1] /* hot */
      }
    }
    catch (err) {
      throw (err)
    }
  }
  return best_user_tweets
}

/* same as above for hashtags. This is embarrasingly repeating myself */
async function generate_hashtag_pages (screen_names, hashtags) {
  for ( let hashtag of hashtags ) {
    try {
      const data = await get_statuses('search/tweets', { q: '#' + hashtag, count: count })
      const statuses = data['statuses']
      const fcodes = await get_fcodes(data['statuses'])
      await ferrify_tweets(data['statuses'], false, hashtag, screen_names, hashtags)
      let indexhtml = await gen_index_page('index_hashtag', fcodes, hashtag, screen_names, hashtags)
      writepage('hashtags/' + hashtag + '/', 'index', indexhtml)
      let best_tweet = await get_best_tweet(data['statuses'])
      if (best_tweet) {
        best_hashtag_tweets[best_tweet[0]] = best_tweet[1]
      }
    }
    catch (err) {
      throw (err)
    }
  }
  return best_hashtag_tweets
}

async function get_statuses(endpoint, options) {
  return T.get(endpoint, options)
  .catch(function (err) {
    console.log('caught: ' + err.stack)
  })
  .then(function (result) {
    return (result.data)
  })
}

function get_fcodes(statuses) {
  let fcodes = {}
  async.each(statuses, function(element, callback) {
    let fcode = alphanumtwid.encode(element['id'])
    fcodes[fcode] = element['text']
  })
  return (fcodes)
}

// maakes a page for all tweets in data, returns list of fcodes for the index
async function ferrify_tweets(data, screen_name, hashtag, screen_names, hashtags) {
  let name = (screen_name || hashtag)
  console.log('  Generating ' + data.length + ' single tweet pages for ' + name)
  for (let element of data) {
      let fcode = alphanumtwid.encode(element['id'])
      let html = await gen_single_page(fcode, element, screen_names, hashtags, hashtag)
      writepage('', fcode, html)
  }
  return data
}

// best_tweet(statuses)
// Return the most favorited tweet for this user or hashtag
async function get_best_tweet(statuses) {
  let best_tweet
  let best_fcode
  statuses.forEach(function(element) {
    let max = 0
    let fcode = alphanumtwid.encode(element['id'])
    if(max < element['favorite_count'] ) {
      max = element['favorite_count']
      best_fcode = fcode
      best_tweet = element['text']
    }
  })
  return [best_fcode, best_tweet]
}

/* genqr(fcode)
Generates a qrcode image file based on the given fcode, returns the filename.
*/
async function genqr (fcode) {
    let qrname='tmp/qr-' + fcode + '.png'
    qrcode.toFile(qrname, my_url + '/' + fcode, function(err) {
    return(qrname)
  })
}

/* genimage(fcode, element)
genimage generates an image with the given details and returns a filename.
element is the result object from the twitter query. */
async function genimage (fcode, element) {
  fs.ensureDirSync('public/img')
  let text = element['text'].replace(/'/g, `’`)
  let screen_name = element['user']['screen_name']
  let created_at = element['created_at']
  /* Call out to a script that does the graphics magick work */
  child_process.exec("sh/make-image.sh '" + text + "' '" + screen_name
    + "' '" + created_at + "' '" + fcode + "'", function(imgname, err) {
    return(imgname)
  })
}

/* Generate a single page for one tweet. */
async function gen_single_page (fcode, element, screen_names, hashtags, hashtag) {
  let qrname = await genqr(fcode)
  genimage(fcode, element)

  let created_at = moment(element['created_at'], 'dd MMM DD HH:mm:ss ZZ YYYY').format('MM/DD/YYYY')
  let context = {
    screen_name: element['user']['screen_name'],
    hashtag: hashtag,
    screen_names: screen_names,
    hashtags: hashtags,
    id: element['id_str'],
    created_at: created_at,
    fcode: fcode,
    text: element['text'],
    my_url: my_url,
    my_twitter: my_twitter,
    my_logo: my_logo
  }
  let tmpl = fs.readFileSync('templates/single.hbs', 'utf8')
  let html = hbsToString(tmpl, context)
  return (html)
}

/* hbsToString(html, context)
Combine handlebars template with data to make html */
function hbsToString (html, context) {
  let template = handlebars.compile(html)
  return template(context)
}

/* writepage(path, filename, html)
Write a single ferrit page to disk. */
async function writepage (path, fname, html) {
  let destpath = 'public/' + path
  fs.ensureDirSync(destpath)
  fs.writeFile(destpath + fname + '.html', html, (err) => {
    if(err) throw err
  })
}

/* Generate an index page for all tweets by one user. */
async function gen_index_page (template, fcodes, name, screen_names, hashtags) {
  console.log('Beginning generation of index with ' + Object.keys(fcodes).length + ' items for ' + name)
  let context = {
    screen_name: name,
    screen_names: screen_names,
    hashtags: hashtags,
    fcodes: fcodes,
    my_url: my_url,
    my_twitter: my_twitter,
    my_logo: my_logo
  }
  let html = fs.readFileSync('templates/' + template + '.hbs', 'utf8')
  return (hbsToString(html, context))
}

// escape single-quotes in strings
function escapeShellArg (arg) {
  string.replace(/\s+/g, " ") // Clean up extraneous spaces
  string.replace(/^\s+|\s+$/g, " ") // Chomp
  return string.replace(/'/g, `"'"`) // Unicode ' isn't special to shell
}

try {
  main()
}
catch (err) {
  console.log(err)
}
<commit_msg>spit polish before first deploy to a real server<commit_after>"use strict"

/* ferrit.js - Reads recent tweets for users or hashtags and generates ferrit
index pages with multiple cards and a ferrit card page for each tweet. */

const Twit = require('twit')
const alphanumtwid = require('alphanumeric-twitter-id')
const child_process = require('child_process')
const qrcode = require('qrcode')
const config = require('config')
const fs = require('fs-extra')
const handlebars = require('handlebars')
const moment = require('moment')
const _ = require('lodash')
const async = require('async')

const uncaught = require('uncaught');

uncaught.start();
uncaught.addListener(function (error) {
    console.log('Uncaught error or rejection: ', error.message);
});

let my_url = config.get('general.my_url')
let my_twitter = config.get('general.my_twitter')
let my_logo = config.get('general.my_logo')

/* Sets up twitter authentiction from the config file. Put it in production.toml
in twitter section and don't check that file into git. */
const T = new Twit( config.get('twitter') );

/* Fun time happy globals! */
//let screen_names =['dril']
//let hashtags = ['ferrets']
let count = 2
let screen_names = _.shuffle(config.get('ferrits.screen_names'))
let hashtags = config.get('ferrits.hashtags')
//let count = config.get('ferrits.count')

let best_user_tweets = {}
let best_hashtag_tweets = {}

/* This is bloody awful. I'm still figuring out how async works in node */
async function main() {
  Promise.all([
      generate_user_pages(screen_names, hashtags),
      generate_hashtag_pages(screen_names, hashtags)
  ]).then(function(results) {
    make_hot_index(results, screen_names, hashtags)
  })
}

async function make_hot_index(best_tweets, screen_names, hashtags) {
  let all_best_tweets = _.assignIn(best_tweets[0], best_tweets[1])
  all_best_tweets = _.pickBy(all_best_tweets, _.identity)
  let html = await gen_index_page('index_hot', all_best_tweets, 'hot', screen_names, hashtags)
  writepage('', 'index', html)
  fs.copySync('assets/', 'public/assets/')
  console.log("Finished!")
}

async function generate_user_pages (screen_names, hashtags) {
  for ( let screen_name of screen_names ) {
    try {
      const statuses = await get_statuses('statuses/user_timeline', { screen_name: screen_name, count: count })
      const fcodes = await get_fcodes(statuses)
      await ferrify_tweets(statuses, screen_name, false, screen_names, hashtags)
      let indexhtml = await gen_index_page('index_user', fcodes, screen_name, screen_names, hashtags)
      writepage(screen_name + '/', 'index', indexhtml)
      let best_tweet = await get_best_tweet(statuses)
      if (best_tweet) {
        best_user_tweets[best_tweet[0]] = best_tweet[1] /* hot */
      }
    }
    catch (err) {
      throw (err)
    }
  }
  return best_user_tweets
}

/* same as above for hashtags. This is embarrasingly repeating myself */
async function generate_hashtag_pages (screen_names, hashtags) {
  for ( let hashtag of hashtags ) {
    try {
      const data = await get_statuses('search/tweets', { q: '#' + hashtag, count: count })
      const statuses = data['statuses']
      const fcodes = await get_fcodes(data['statuses'])
      await ferrify_tweets(data['statuses'], false, hashtag, screen_names, hashtags)
      let indexhtml = await gen_index_page('index_hashtag', fcodes, hashtag, screen_names, hashtags)
      writepage('hashtags/' + hashtag + '/', 'index', indexhtml)
      let best_tweet = await get_best_tweet(data['statuses'])
      if (best_tweet) {
        best_hashtag_tweets[best_tweet[0]] = best_tweet[1]
      }
    }
    catch (err) {
      throw (err)
    }
  }
  return best_hashtag_tweets
}

async function get_statuses(endpoint, options) {
  return T.get(endpoint, options)
  .catch(function (err) {
    console.log('caught: ' + err.stack)
  })
  .then(function (result) {
    return (result.data)
  })
}

function get_fcodes(statuses) {
  let fcodes = {}
  async.each(statuses, function(element, callback) {
    let fcode = alphanumtwid.encode(element['id'])
    fcodes[fcode] = element['text']
  })
  return (fcodes)
}

// maakes a page for all tweets in data, returns list of fcodes for the index
async function ferrify_tweets(data, screen_name, hashtag, screen_names, hashtags) {
  let name = (screen_name || hashtag)
  console.log('  Generating ' + data.length + ' single tweet pages for ' + name)
  for (let element of data) {
      let fcode = alphanumtwid.encode(element['id'])
      let html = await gen_single_page(fcode, element, screen_names, hashtags, hashtag)
      writepage('', fcode, html)
  }
  return data
}

// best_tweet(statuses)
// Return the most favorited tweet for this user or hashtag
async function get_best_tweet(statuses) {
  let best_tweet
  let best_fcode
  statuses.forEach(function(element) {
    let max = 0
    let fcode = alphanumtwid.encode(element['id'])
    if(max < element['favorite_count'] ) {
      max = element['favorite_count']
      best_fcode = fcode
      best_tweet = element['text']
    }
  })
  return [best_fcode, best_tweet]
}

/* genqr(fcode)
Generates a qrcode image file based on the given fcode, returns the filename.
*/
async function genqr (fcode) {
    let qrname='tmp/qr-' + fcode + '.png'
    qrcode.toFile(qrname, my_url + '/' + fcode, function(err) {
    return(qrname)
  })
}

/* genimage(fcode, element)
genimage generates an image with the given details and returns a filename.
element is the result object from the twitter query. */
async function genimage (fcode, element) {
  fs.ensureDirSync('public/img')
  let text = element['text'].replace(/'/g, `’`)
  let screen_name = element['user']['screen_name']
  let created_at = element['created_at']
  /* Call out to a script that does the graphics magick work */
  child_process.exec("sh/make-image.sh '" + text + "' '" + screen_name
    + "' '" + created_at + "' '" + fcode + "'", function(imgname, err) {
    return(imgname)
  })
}

/* Generate a single page for one tweet. */
async function gen_single_page (fcode, element, screen_names, hashtags, hashtag) {
  let qrname = await genqr(fcode)
  genimage(fcode, element)

  let created_at = moment(element['created_at'], 'dd MMM DD HH:mm:ss ZZ YYYY').format('MM/DD/YYYY')
  let context = {
    screen_name: element['user']['screen_name'],
    hashtag: hashtag,
    screen_names: screen_names,
    hashtags: hashtags,
    id: element['id_str'],
    created_at: created_at,
    fcode: fcode,
    text: element['text'],
    my_url: my_url,
    my_twitter: my_twitter,
    my_logo: my_logo
  }
  let tmpl = fs.readFileSync('templates/single.hbs', 'utf8')
  let html = hbsToString(tmpl, context)
  return (html)
}

/* hbsToString(html, context)
Combine handlebars template with data to make html */
function hbsToString (html, context) {
  let template = handlebars.compile(html)
  return template(context)
}

/* writepage(path, filename, html)
Write a single ferrit page to disk. */
async function writepage (path, fname, html) {
  let destpath = 'public/' + path
  fs.ensureDirSync(destpath)
  fs.writeFile(destpath + fname + '.html', html, (err) => {
    if(err) throw err
  })
}

/* Generate an index page for all tweets by one user. */
async function gen_index_page (template, fcodes, name, screen_names, hashtags) {
  console.log('Beginning generation of index with ' + Object.keys(fcodes).length + ' items for ' + name)
  let context = {
    screen_name: name,
    screen_names: screen_names,
    hashtags: hashtags,
    fcodes: fcodes,
    my_url: my_url,
    my_twitter: my_twitter,
    my_logo: my_logo
  }
  let html = fs.readFileSync('templates/' + template + '.hbs', 'utf8')
  return (hbsToString(html, context))
}

// escape single-quotes in strings
function escapeShellArg (arg) {
  string.replace(/\s+/g, " ") // Clean up extraneous spaces
  string.replace(/^\s+|\s+$/g, " ") // Chomp
  return string.replace(/'/g, `"'"`) // Unicode ' isn't special to shell
}

try {
  main()
}
catch (err) {
  console.log(err)
}
<|endoftext|>


<commit_before>'use strict';
var uuid, Service, Characteristic, StreamController;

var crypto = require('crypto');
var fs = require('fs');
var ip = require('ip');
var spawn = require('child_process').spawn;
var drive = require('./drive').drive;

module.exports = {
  FFMPEG: FFMPEG
};

function FFMPEG(hap, cameraConfig, log, videoProcessor) {
  uuid = hap.uuid;
  Service = hap.Service;
  Characteristic = hap.Characteristic;
  StreamController = hap.StreamController;
  this.log = log;

  var ffmpegOpt = cameraConfig.videoConfig;
  this.name = cameraConfig.name;
  this.vcodec = ffmpegOpt.vcodec;
  this.videoProcessor = videoProcessor || 'ffmpeg';
  this.audio = ffmpegOpt.audio;
  this.acodec = ffmpegOpt.acodec;
  this.packetsize = ffmpegOpt.packetSize
  this.fps = ffmpegOpt.maxFPS || 10;
  this.maxBitrate = ffmpegOpt.maxBitrate || 300;
  this.debug = ffmpegOpt.debug;
  this.additionalCommandline = ffmpegOpt.additionalCommandline || '-tune zerolatency';

  if (!ffmpegOpt.source) {
    throw new Error("Missing source for camera.");
  }

  this.ffmpegSource = ffmpegOpt.source;
  this.ffmpegImageSource = ffmpegOpt.stillImageSource;

  this.services = [];
  this.streamControllers = [];

  this.pendingSessions = {};
  this.ongoingSessions = {};

  this.uploader = cameraConfig.uploader || false;
  if ( this.uploader )
    { this.drive = new drive(); }

  var numberOfStreams = ffmpegOpt.maxStreams || 2;
  var videoResolutions = [];

  this.maxWidth = ffmpegOpt.maxWidth || 1280;
  this.maxHeight = ffmpegOpt.maxHeight || 720;
  var maxFPS = (this.fps > 30) ? 30 : this.fps;

  if (this.maxWidth >= 320) {
    if (this.maxHeight >= 240) {
      videoResolutions.push([320, 240, maxFPS]);
      if (maxFPS > 15) {
        videoResolutions.push([320, 240, 15]);
      }
    }

    if (this.maxHeight >= 180) {
      videoResolutions.push([320, 180, maxFPS]);
      if (maxFPS > 15) {
        videoResolutions.push([320, 180, 15]);
      }
    }
  }

  if (this.maxWidth >= 480) {
    if (this.maxHeight >= 360) {
      videoResolutions.push([480, 360, maxFPS]);
    }

    if (this.maxHeight >= 270) {
      videoResolutions.push([480, 270, maxFPS]);
    }
  }

  if (this.maxWidth >= 640) {
    if (this.maxHeight >= 480) {
      videoResolutions.push([640, 480, maxFPS]);
    }

    if (this.maxHeight >= 360) {
      videoResolutions.push([640, 360, maxFPS]);
    }
  }

  if (this.maxWidth >= 1280) {
    if (this.maxHeight >= 960) {
      videoResolutions.push([1280, 960, maxFPS]);
    }

    if (this.maxHeight >= 720) {
      videoResolutions.push([1280, 720, maxFPS]);
    }
  }

  if (this.maxWidth >= 1920) {
    if (this.maxHeight >= 1080) {
      videoResolutions.push([1920, 1080, maxFPS]);
    }
  }

  let options = {
    proxy: false, // Requires RTP/RTCP MUX Proxy
    srtp: true, // Supports SRTP AES_CM_128_HMAC_SHA1_80 encryption
    video: {
      resolutions: videoResolutions,
      codec: {
        profiles: [0, 1, 2], // Enum, please refer StreamController.VideoCodecParamProfileIDTypes
        levels: [0, 1, 2] // Enum, please refer StreamController.VideoCodecParamLevelTypes
      }
    },
    audio: {
      codecs: [
        {
          type: "OPUS", // Audio Codec
          samplerate: 24 // 8, 16, 24 KHz
        },
        {
          type: "AAC-eld",
          samplerate: 16
        }
      ]
    }
  }

  this.createCameraControlService();
  this._createStreamControllers(numberOfStreams, options);
}

FFMPEG.prototype.handleCloseConnection = function(connectionID) {
  this.streamControllers.forEach(function(controller) {
    controller.handleCloseConnection(connectionID);
  });
}

FFMPEG.prototype.handleSnapshotRequest = function(request, callback) {
  let resolution = request.width + 'x' + request.height;
  var imageSource = this.ffmpegImageSource !== undefined ? this.ffmpegImageSource : this.ffmpegSource;
  let ffmpeg = spawn(this.videoProcessor, (imageSource + ' -t 1 -s '+ resolution + ' -f image2 -').split(' '), {env: process.env});
  var imageBuffer = Buffer(0);
  this.log("Snapshot from " + this.name + " at " + resolution);
  if(this.debug) console.log('ffmpeg '+imageSource + ' -t 1 -s '+ resolution + ' -f image2 -');
  ffmpeg.stdout.on('data', function(data) {
    imageBuffer = Buffer.concat([imageBuffer, data]);
  });
  let self = this;
  ffmpeg.on('error', function(error){
    self.log("An error occurs while making snapshot request");
    self.debug ? self.log(error) : null;
  });
  ffmpeg.on('close', function(code) {
    if ( this.uploader )
      { this.drive.storePicture(this.name,imageBuffer); }
    callback(undefined, imageBuffer);
  }.bind(this));
}

FFMPEG.prototype.prepareStream = function(request, callback) {
  var sessionInfo = {};

  let sessionID = request["sessionID"];
  let targetAddress = request["targetAddress"];

  sessionInfo["address"] = targetAddress;

  var response = {};

  let videoInfo = request["video"];
  if (videoInfo) {
    let targetPort = videoInfo["port"];
    let srtp_key = videoInfo["srtp_key"];
    let srtp_salt = videoInfo["srtp_salt"];

    // SSRC is a 32 bit integer that is unique per stream
    let ssrcSource = crypto.randomBytes(4);
    ssrcSource[0] = 0;
    let ssrc = ssrcSource.readInt32BE(0, true);

    let videoResp = {
      port: targetPort,
      ssrc: ssrc,
      srtp_key: srtp_key,
      srtp_salt: srtp_salt
    };

    response["video"] = videoResp;

    sessionInfo["video_port"] = targetPort;
    sessionInfo["video_srtp"] = Buffer.concat([srtp_key, srtp_salt]);
    sessionInfo["video_ssrc"] = ssrc;
  }

  let audioInfo = request["audio"];
  if (audioInfo) {
    let targetPort = audioInfo["port"];
    let srtp_key = audioInfo["srtp_key"];
    let srtp_salt = audioInfo["srtp_salt"];

    // SSRC is a 32 bit integer that is unique per stream
    let ssrcSource = crypto.randomBytes(4);
    ssrcSource[0] = 0;
    let ssrc = ssrcSource.readInt32BE(0, true);

    let audioResp = {
      port: targetPort,
      ssrc: ssrc,
      srtp_key: srtp_key,
      srtp_salt: srtp_salt
    };

    response["audio"] = audioResp;

    sessionInfo["audio_port"] = targetPort;
    sessionInfo["audio_srtp"] = Buffer.concat([srtp_key, srtp_salt]);
    sessionInfo["audio_ssrc"] = ssrc;
  }

  let currentAddress = ip.address();
  var addressResp = {
    address: currentAddress
  };

  if (ip.isV4Format(currentAddress)) {
    addressResp["type"] = "v4";
  } else {
    addressResp["type"] = "v6";
  }

  response["address"] = addressResp;
  this.pendingSessions[uuid.unparse(sessionID)] = sessionInfo;

  callback(response);
}

FFMPEG.prototype.handleStreamRequest = function(request) {
  var sessionID = request["sessionID"];
  var requestType = request["type"];
  if (sessionID) {
    let sessionIdentifier = uuid.unparse(sessionID);

    if (requestType == "start") {
      var sessionInfo = this.pendingSessions[sessionIdentifier];
      if (sessionInfo) {
        var width = 1280;
        var height = 720;
        var fps = this.fps || 30;
        var vbitrate = this.maxBitrate;
        var abitrate = 32;
        var asamplerate = 16;
        var vcodec = this.vcodec || 'libx264';
        var acodec = this.acodec || 'libfdk_aac';
        var packetsize = this.packetsize || 1316; // 188 376
        var additionalCommandline = this.additionalCommandline ;

        let videoInfo = request["video"];
        if (videoInfo) {
          width = videoInfo["width"];
          height = videoInfo["height"];

          let expectedFPS = videoInfo["fps"];
          if (expectedFPS < fps) {
            fps = expectedFPS;
          }
          if(videoInfo["max_bit_rate"] < vbitrate) {
            vbitrate = videoInfo["max_bit_rate"];
          }
        }

        let audioInfo = request["audio"];
        if (audioInfo) {
          abitrate = audioInfo["max_bit_rate"];
          asamplerate = audioInfo["sample_rate"];
        }

        let targetAddress = sessionInfo["address"];
        let targetVideoPort = sessionInfo["video_port"];
        let videoKey = sessionInfo["video_srtp"];
        let videoSsrc = sessionInfo["video_ssrc"];
        let targetAudioPort = sessionInfo["audio_port"];
        let audioKey = sessionInfo["audio_srtp"];
        let audioSsrc = sessionInfo["audio_ssrc"];

        let ffmpegCommand = this.ffmpegSource + ' -map 0:0' +
          ' -vcodec ' + vcodec +
          ' -pix_fmt yuv420p' +
          ' -r ' + fps +
          ' -f rawvideo' +
          ' ' + additionalCommandline +
          ((vcodec !== 'copy') ? (' -vf scale=' + width + ':' + height) : '') +
          ' -b:v ' + vbitrate + 'k' +
          ' -bufsize ' + vbitrate+ 'k' +
          ' -maxrate '+ vbitrate + 'k' +
          ' -payload_type 99' +
          ' -ssrc ' + videoSsrc +
          ' -f rtp' +
          ' -srtp_out_suite AES_CM_128_HMAC_SHA1_80' +
          ' -srtp_out_params ' + videoKey.toString('base64') +
          ' srtp://' + targetAddress + ':' + targetVideoPort +
          '?rtcpport=' + targetVideoPort +
          '&localrtcpport=' + targetVideoPort +
          '&pkt_size=' + packetsize;

        if(this.audio){
          ffmpegCommand+= ' -map 0:1' +
            ' -acodec ' + acodec +
            ' -profile:a aac_eld' +
            ' -flags +global_header' +
            ' -f null' +
            ' -ar ' + asamplerate + 'k' +
            ' -b:a ' + abitrate + 'k' +
            ' -bufsize ' + abitrate + 'k' +
            ' -ac 1' +
            ' -payload_type 110' +
            ' -ssrc ' + audioSsrc +
            ' -f rtp' +
            ' -srtp_out_suite AES_CM_128_HMAC_SHA1_80' +
            ' -srtp_out_params ' + audioKey.toString('base64') +
            ' srtp://' + targetAddress + ':' + targetAudioPort +
            '?rtcpport=' + targetAudioPort +
            '&localrtcpport=' + targetAudioPort +
            '&pkt_size=' + packetsize;
        }

        let ffmpeg = spawn(this.videoProcessor, ffmpegCommand.split(' '), {env: process.env});
        this.log("Start streaming video from " + this.name + " with " + width + "x" + height + "@" + vbitrate + "kBit");
        if(this.debug){
          console.log("ffmpeg " + ffmpegCommand);
        }

        // Always setup hook on stderr.
        // Without this streaming stops within one to two minutes.
        ffmpeg.stderr.on('data', function(data) {
          // Do not log to the console if debugging is turned off
          if(this.debug){
            console.log(data.toString());
          }
        });
        let self = this;
        ffmpeg.on('error', function(error){
            self.log("An error occurs while making stream request");
            self.debug ? self.log(error) : null;
        });
        ffmpeg.on('close', (code) => {
          if(code == null || code == 0 || code == 255){
            self.log("Stopped streaming");
          } else {
            self.log("ERROR: FFmpeg exited with code " + code);
            for(var i=0; i < self.streamControllers.length; i++){
              var controller = self.streamControllers[i];
              if(controller.sessionIdentifier === sessionID){
                controller.forceStop();
              }
            }
          }
        });
        this.ongoingSessions[sessionIdentifier] = ffmpeg;
      }

      delete this.pendingSessions[sessionIdentifier];
    } else if (requestType == "stop") {
      var ffmpegProcess = this.ongoingSessions[sessionIdentifier];
      if (ffmpegProcess) {
        ffmpegProcess.kill('SIGTERM');
      }
      delete this.ongoingSessions[sessionIdentifier];
    }
  }
}

FFMPEG.prototype.createCameraControlService = function() {
  var controlService = new Service.CameraControl();

  this.services.push(controlService);

  if(this.audio){
    var microphoneService = new Service.Microphone();
    this.services.push(microphoneService);
  }
}

// Private

FFMPEG.prototype._createStreamControllers = function(maxStreams, options) {
  let self = this;

  for (var i = 0; i < maxStreams; i++) {
    var streamController = new StreamController(i, options, self);

    self.services.push(streamController.service);
    self.streamControllers.push(streamController);
  }
}<commit_msg>Add VideoFilter support<commit_after>'use strict';
var uuid, Service, Characteristic, StreamController;

var crypto = require('crypto');
var fs = require('fs');
var ip = require('ip');
var spawn = require('child_process').spawn;
var drive = require('./drive').drive;

module.exports = {
  FFMPEG: FFMPEG
};

function FFMPEG(hap, cameraConfig, log, videoProcessor) {
  uuid = hap.uuid;
  Service = hap.Service;
  Characteristic = hap.Characteristic;
  StreamController = hap.StreamController;
  this.log = log;

  var ffmpegOpt = cameraConfig.videoConfig;
  this.name = cameraConfig.name;
  this.vcodec = ffmpegOpt.vcodec;
  this.videoProcessor = videoProcessor || 'ffmpeg';
  this.audio = ffmpegOpt.audio;
  this.acodec = ffmpegOpt.acodec;
  this.packetsize = ffmpegOpt.packetSize
  this.fps = ffmpegOpt.maxFPS || 10;
  this.maxBitrate = ffmpegOpt.maxBitrate || 300;
  this.debug = ffmpegOpt.debug;
  this.additionalCommandline = ffmpegOpt.additionalCommandline || '-tune zerolatency';
  this.vflip = ffmpegOpt.vflip || false;
  this.hflip = ffmpegOpt.hflip || false;
  this.videoFilter = ffmpegOpt.videoFilter || null; // null is a valid discrete value

  if (!ffmpegOpt.source) {
    throw new Error("Missing source for camera.");
  }

  this.ffmpegSource = ffmpegOpt.source;
  this.ffmpegImageSource = ffmpegOpt.stillImageSource;

  this.services = [];
  this.streamControllers = [];

  this.pendingSessions = {};
  this.ongoingSessions = {};

  this.uploader = cameraConfig.uploader || false;
  if ( this.uploader )
    { this.drive = new drive(); }

  var numberOfStreams = ffmpegOpt.maxStreams || 2;
  var videoResolutions = [];

  this.maxWidth = ffmpegOpt.maxWidth || 1280;
  this.maxHeight = ffmpegOpt.maxHeight || 720;
  var maxFPS = (this.fps > 30) ? 30 : this.fps;

  if (this.maxWidth >= 320) {
    if (this.maxHeight >= 240) {
      videoResolutions.push([320, 240, maxFPS]);
      if (maxFPS > 15) {
        videoResolutions.push([320, 240, 15]);
      }
    }

    if (this.maxHeight >= 180) {
      videoResolutions.push([320, 180, maxFPS]);
      if (maxFPS > 15) {
        videoResolutions.push([320, 180, 15]);
      }
    }
  }

  if (this.maxWidth >= 480) {
    if (this.maxHeight >= 360) {
      videoResolutions.push([480, 360, maxFPS]);
    }

    if (this.maxHeight >= 270) {
      videoResolutions.push([480, 270, maxFPS]);
    }
  }

  if (this.maxWidth >= 640) {
    if (this.maxHeight >= 480) {
      videoResolutions.push([640, 480, maxFPS]);
    }

    if (this.maxHeight >= 360) {
      videoResolutions.push([640, 360, maxFPS]);
    }
  }

  if (this.maxWidth >= 1280) {
    if (this.maxHeight >= 960) {
      videoResolutions.push([1280, 960, maxFPS]);
    }

    if (this.maxHeight >= 720) {
      videoResolutions.push([1280, 720, maxFPS]);
    }
  }

  if (this.maxWidth >= 1920) {
    if (this.maxHeight >= 1080) {
      videoResolutions.push([1920, 1080, maxFPS]);
    }
  }

  let options = {
    proxy: false, // Requires RTP/RTCP MUX Proxy
    srtp: true, // Supports SRTP AES_CM_128_HMAC_SHA1_80 encryption
    video: {
      resolutions: videoResolutions,
      codec: {
        profiles: [0, 1, 2], // Enum, please refer StreamController.VideoCodecParamProfileIDTypes
        levels: [0, 1, 2] // Enum, please refer StreamController.VideoCodecParamLevelTypes
      }
    },
    audio: {
      codecs: [
        {
          type: "OPUS", // Audio Codec
          samplerate: 24 // 8, 16, 24 KHz
        },
        {
          type: "AAC-eld",
          samplerate: 16
        }
      ]
    }
  }

  this.createCameraControlService();
  this._createStreamControllers(numberOfStreams, options);
}

FFMPEG.prototype.handleCloseConnection = function(connectionID) {
  this.streamControllers.forEach(function(controller) {
    controller.handleCloseConnection(connectionID);
  });
}

FFMPEG.prototype.handleSnapshotRequest = function(request, callback) {
  let resolution = request.width + 'x' + request.height;
  var imageSource = this.ffmpegImageSource !== undefined ? this.ffmpegImageSource : this.ffmpegSource;
  let ffmpeg = spawn(this.videoProcessor, (imageSource + ' -t 1 -s '+ resolution + ' -f image2 -').split(' '), {env: process.env});
  var imageBuffer = Buffer(0);
  this.log("Snapshot from " + this.name + " at " + resolution);
  if(this.debug) console.log('ffmpeg '+imageSource + ' -t 1 -s '+ resolution + ' -f image2 -');
  ffmpeg.stdout.on('data', function(data) {
    imageBuffer = Buffer.concat([imageBuffer, data]);
  });
  let self = this;
  ffmpeg.on('error', function(error){
    self.log("An error occurs while making snapshot request");
    self.debug ? self.log(error) : null;
  });
  ffmpeg.on('close', function(code) {
    if ( this.uploader )
      { this.drive.storePicture(this.name,imageBuffer); }
    callback(undefined, imageBuffer);
  }.bind(this));
}

FFMPEG.prototype.prepareStream = function(request, callback) {
  var sessionInfo = {};

  let sessionID = request["sessionID"];
  let targetAddress = request["targetAddress"];

  sessionInfo["address"] = targetAddress;

  var response = {};

  let videoInfo = request["video"];
  if (videoInfo) {
    let targetPort = videoInfo["port"];
    let srtp_key = videoInfo["srtp_key"];
    let srtp_salt = videoInfo["srtp_salt"];

    // SSRC is a 32 bit integer that is unique per stream
    let ssrcSource = crypto.randomBytes(4);
    ssrcSource[0] = 0;
    let ssrc = ssrcSource.readInt32BE(0, true);

    let videoResp = {
      port: targetPort,
      ssrc: ssrc,
      srtp_key: srtp_key,
      srtp_salt: srtp_salt
    };

    response["video"] = videoResp;

    sessionInfo["video_port"] = targetPort;
    sessionInfo["video_srtp"] = Buffer.concat([srtp_key, srtp_salt]);
    sessionInfo["video_ssrc"] = ssrc;
  }

  let audioInfo = request["audio"];
  if (audioInfo) {
    let targetPort = audioInfo["port"];
    let srtp_key = audioInfo["srtp_key"];
    let srtp_salt = audioInfo["srtp_salt"];

    // SSRC is a 32 bit integer that is unique per stream
    let ssrcSource = crypto.randomBytes(4);
    ssrcSource[0] = 0;
    let ssrc = ssrcSource.readInt32BE(0, true);

    let audioResp = {
      port: targetPort,
      ssrc: ssrc,
      srtp_key: srtp_key,
      srtp_salt: srtp_salt
    };

    response["audio"] = audioResp;

    sessionInfo["audio_port"] = targetPort;
    sessionInfo["audio_srtp"] = Buffer.concat([srtp_key, srtp_salt]);
    sessionInfo["audio_ssrc"] = ssrc;
  }

  let currentAddress = ip.address();
  var addressResp = {
    address: currentAddress
  };

  if (ip.isV4Format(currentAddress)) {
    addressResp["type"] = "v4";
  } else {
    addressResp["type"] = "v6";
  }

  response["address"] = addressResp;
  this.pendingSessions[uuid.unparse(sessionID)] = sessionInfo;

  callback(response);
}

FFMPEG.prototype.handleStreamRequest = function(request) {
  var sessionID = request["sessionID"];
  var requestType = request["type"];
  if (sessionID) {
    let sessionIdentifier = uuid.unparse(sessionID);

    if (requestType == "start") {
      var sessionInfo = this.pendingSessions[sessionIdentifier];
      if (sessionInfo) {
        var width = 1280;
        var height = 720;
        var fps = this.fps || 30;
        var vbitrate = this.maxBitrate;
        var abitrate = 32;
        var asamplerate = 16;
        var vcodec = this.vcodec || 'libx264';
        var acodec = this.acodec || 'libfdk_aac';
        var packetsize = this.packetsize || 1316; // 188 376
        var additionalCommandline = this.additionalCommandline ;

        let videoInfo = request["video"];
        if (videoInfo) {
          width = videoInfo["width"];
          height = videoInfo["height"];

          let expectedFPS = videoInfo["fps"];
          if (expectedFPS < fps) {
            fps = expectedFPS;
          }
          if(videoInfo["max_bit_rate"] < vbitrate) {
            vbitrate = videoInfo["max_bit_rate"];
          }
        }

        let audioInfo = request["audio"];
        if (audioInfo) {
          abitrate = audioInfo["max_bit_rate"];
          asamplerate = audioInfo["sample_rate"];
        }

        let targetAddress = sessionInfo["address"];
        let targetVideoPort = sessionInfo["video_port"];
        let videoKey = sessionInfo["video_srtp"];
        let videoSsrc = sessionInfo["video_ssrc"];
        let targetAudioPort = sessionInfo["audio_port"];
        let audioKey = sessionInfo["audio_srtp"];
        let audioSsrc = sessionInfo["audio_ssrc"];
        let vf = [];

        let videoFilter = ((this.videoFilter === '') ? ('scale=' + width + ':' + height + '') : (this.videoFilter)); // empty string indicates default
        // In the case of null, skip entirely
        if (videoFilter !== null && videoFilter !== 'none') {
          vf.push(videoFilter)

          if(this.hflip)
            vf.push('hflip');

          if(this.vflip)
            vf.push('vflip');
        }

        let ffmpegCommand = this.ffmpegSource + ' -map 0:0' +
          ' -vcodec ' + vcodec +
          ' -pix_fmt yuv420p' +
          ' -r ' + fps +
          ' -f rawvideo' +
          ' ' + additionalCommandline +
          ((vcodec !== 'copy' && vf.length > 0) ? (' -vf ' + vf.join(',')) : ('')) +
          ' -b:v ' + vbitrate + 'k' +
          ' -bufsize ' + vbitrate+ 'k' +
          ' -maxrate '+ vbitrate + 'k' +
          ' -payload_type 99' +
          ' -ssrc ' + videoSsrc +
          ' -f rtp' +
          ' -srtp_out_suite AES_CM_128_HMAC_SHA1_80' +
          ' -srtp_out_params ' + videoKey.toString('base64') +
          ' srtp://' + targetAddress + ':' + targetVideoPort +
          '?rtcpport=' + targetVideoPort +
          '&localrtcpport=' + targetVideoPort +
          '&pkt_size=' + packetsize;

        if(this.audio){
          ffmpegCommand+= ' -map 0:1' +
            ' -acodec ' + acodec +
            ' -profile:a aac_eld' +
            ' -flags +global_header' +
            ' -f null' +
            ' -ar ' + asamplerate + 'k' +
            ' -b:a ' + abitrate + 'k' +
            ' -bufsize ' + abitrate + 'k' +
            ' -ac 1' +
            ' -payload_type 110' +
            ' -ssrc ' + audioSsrc +
            ' -f rtp' +
            ' -srtp_out_suite AES_CM_128_HMAC_SHA1_80' +
            ' -srtp_out_params ' + audioKey.toString('base64') +
            ' srtp://' + targetAddress + ':' + targetAudioPort +
            '?rtcpport=' + targetAudioPort +
            '&localrtcpport=' + targetAudioPort +
            '&pkt_size=' + packetsize;
        }

        let ffmpeg = spawn(this.videoProcessor, ffmpegCommand.split(' '), {env: process.env});
        this.log("Start streaming video from " + this.name + " with " + width + "x" + height + "@" + vbitrate + "kBit");
        if(this.debug){
          console.log("ffmpeg " + ffmpegCommand);
        }

        // Always setup hook on stderr.
        // Without this streaming stops within one to two minutes.
        ffmpeg.stderr.on('data', function(data) {
          // Do not log to the console if debugging is turned off
          if(this.debug){
            console.log(data.toString());
          }
        });
        let self = this;
        ffmpeg.on('error', function(error){
            self.log("An error occurs while making stream request");
            self.debug ? self.log(error) : null;
        });
        ffmpeg.on('close', (code) => {
          if(code == null || code == 0 || code == 255){
            self.log("Stopped streaming");
          } else {
            self.log("ERROR: FFmpeg exited with code " + code);
            for(var i=0; i < self.streamControllers.length; i++){
              var controller = self.streamControllers[i];
              if(controller.sessionIdentifier === sessionID){
                controller.forceStop();
              }
            }
          }
        });
        this.ongoingSessions[sessionIdentifier] = ffmpeg;
      }

      delete this.pendingSessions[sessionIdentifier];
    } else if (requestType == "stop") {
      var ffmpegProcess = this.ongoingSessions[sessionIdentifier];
      if (ffmpegProcess) {
        ffmpegProcess.kill('SIGTERM');
      }
      delete this.ongoingSessions[sessionIdentifier];
    }
  }
}

FFMPEG.prototype.createCameraControlService = function() {
  var controlService = new Service.CameraControl();

  this.services.push(controlService);

  if(this.audio){
    var microphoneService = new Service.Microphone();
    this.services.push(microphoneService);
  }
}

// Private

FFMPEG.prototype._createStreamControllers = function(maxStreams, options) {
  let self = this;

  for (var i = 0; i < maxStreams; i++) {
    var streamController = new StreamController(i, options, self);

    self.services.push(streamController.service);
    self.streamControllers.push(streamController);
  }
}
<|endoftext|>


<commit_before>function airline_init_pre() {
  prop.airline = {};
  prop.airline.airlines = {};
}

/**
 * An aircrcraft operating agency
 */
zlsa.atc.Airline = Fiber.extend(function() {
  return {
    /**
     * Create new airline
     */
    init: function (icao, options) {
      /** ICAO airline designation */
      this.icao = "YYY";

      /** Agency name */
      this.name = "Default airline";

      /** Radio callsign */
      this.callsign = 'Default';

      /** Parameters for flight number generation */
      this.flightNumberGeneration = {
        /** How many characters in the flight number */
        length: 3,
        /** Whether to use alphabetical characters */
        alpha: false,
      };

      /** Named weighted sets of aircraft */
      this.fleets = {
        default: [],
      };

      this.parse(icao, options);
    },

    /**
     * Initialize object from data
     */
    parse: function (icao, data) {
      if (data.icao)
        this.icao = data.icao;
      else
        this.icao = icao;

      this.name = data.name;
      this.callsign = data.callsign.name;
      this.flightNumberGeneration.length = data.callsign.length;
      this.flightNumberGeneration.alpha = (data.callsign.alpha === true);
      if (data.fleets)
        this.fleets = data.fleets;
      else
        this.fleets.default = data.aircraft;

      for (var f in this.fleets) {
        for (var j=0;j<this.fleets[f].length;j++) {
          this.fleets[f][j][0] = this.fleets[f][j][0].toLowerCase();
        }
      }
    },

    /**
     * Return a random ICAO aircraft designator from the given fleet
     *
     * If no fleet is specified the default fleet is used
     */
    chooseAircraft: function (fleet) {
      if (!fleet)
        fleet = 'default';

      try {
        return choose_weight(this.fleets[fleet.toLowerCase()]);
      }
      catch (e) {
        console.log("Unable to find fleet " + fleet
                    + " for airline " + this.icao);
        throw e;
      }
    },

    /**
     * Create a flight number/identifier
     */
    generateFlightNumber: function () {
      var flightNumberLength = this.flightNumberGeneration.length;
      var flightNumber = "";

      var list = "0123456789";

      // Start with a number other than zero
      flightNumber += choose(list.substr(1));

      if (this.flightNumberGeneration.alpha) {
        for (var i=0; i<flightNumberLength - 3; i++)
          flightNumber += choose(list);
        list = "abcdefghijklmnopqrstuvwxyz";
        for (var i=0; i<2; i++)
          flightNumber += choose(list);
      } else {
        for (var i=1; i<flightNumberLength;i++)
          flightNumber += choose(list);
      }
      return flightNumber;
    },

    /**
     * Checks all fleets for valid aircraft identifiers and log errors
     */
    validateFleets: function () {
      for (var f in this.fleets) {
        for (var j=0;j<this.fleets[f].length;j++) {
          if (!(this.fleets[f][j][0] in prop.aircraft.models)) {
            console.warn("Airline " + this.icao.toUpperCase()
                         + " uses nonexistent aircraft " + this.fleets[f][j][0]
                         + ", expect errors");
          }

          if (typeof this.fleets[f][j][1] != "number") {
            console.warn("Airline " + this.icao.toUpperCase()
                         + " uses non numeric weight for aircraft " +
                         this.fleets[f][j][0] + ", expect errors");
          }
        }
      }
    },
  };
});

function airline_init() {
  airline_load("AAL");
  airline_load("ACA");
  airline_load("AEA");
  airline_load("AFL");
  airline_load("AFR");
  airline_load("AIC");
  airline_load("AIRTAXI");
  airline_load("AMX");
  airline_load("ARG");
  airline_load("ASA");
  airline_load("ASQ");
  airline_load("AUA");
  airline_load("AVA");
  airline_load("AWE");
  airline_load("AWI");
  airline_load("AWQ");
  airline_load("AXB");
  airline_load("AZA");
  airline_load("AZU");
  airline_load("BAW");
  airline_load("BCY");
  airline_load("BEE");
  airline_load("BEL");
  airline_load("BER");
  airline_load("BTK");
  airline_load("BWA");
  airline_load("CCA");
  airline_load("CES");
  airline_load("CESSNA");
  airline_load("CFG");
  airline_load("CFS");
  airline_load("CPA");
  airline_load("CPZ");
  airline_load("CSN");
  airline_load("CTV");
  airline_load("CWC");
  airline_load("DAL");
  airline_load("DLH");
  airline_load("EIN");
  airline_load("EMBRAER");
  airline_load("ENY");
  airline_load("ETD");
  airline_load("EXS");
  airline_load("EVA");
  airline_load("EZY");
  airline_load("FAB");
  airline_load("FASTGA");
  airline_load("FDB");
  airline_load("FDX");
  airline_load("FFT");
  airline_load("FLG");
  airline_load("FWI");
  airline_load("GIA");
  airline_load("GJS");
  airline_load("GLO");
  airline_load("GOW");
  airline_load("GWI");
  airline_load("HAL");
  airline_load("HDA");
  airline_load("IAD");
  airline_load("IBE");
  airline_load("IGO");
  airline_load("INC");
  airline_load("JAI");
  airline_load("JAL");
  airline_load("JBU");
  airline_load("JIA");
  airline_load("KAL");
  airline_load("KAP");
  airline_load("KLC");
  airline_load("KLM");
  airline_load("LAN");
  airline_load("LIGHTGA");
  airline_load("LLR");
  airline_load("LNI");
  airline_load("MON");
  airline_load("MOV");
  airline_load("NAX");
  airline_load("NKS");
  airline_load("ONE");
  airline_load("PAA");
  airline_load("PDT");
  airline_load("QFA");
  airline_load("QXE");
  airline_load("RFF");
  airline_load("RLU");
  airline_load("RPA");
  airline_load("RYR");
  airline_load("SAS");
  airline_load("SBI");
  airline_load("SCO");
  airline_load("SCX");
  airline_load("SEJ");
  airline_load("SIA");
  airline_load("SJY");
  airline_load("SKW");
  airline_load("SLK");
  airline_load("SVR");
  airline_load("SWA");
  airline_load("SWR");
  airline_load("SXD");
  airline_load("TAM");
  airline_load("TCF");
  airline_load("TCX");
  airline_load("TGw");
  airline_load("THY");
  airline_load("TOM");
  airline_load("TSO");
  airline_load("TUI");
  airline_load("TWA");
  airline_load("TYA");
  airline_load("UAE");
  airline_load("UAL");
  airline_load("UPS");
  airline_load("USAF");
  airline_load("VIR");
  airline_load("VRD");
  airline_load("VTI");
  airline_load("WIA");
  airline_load("XAX");
}

function airline_load(icao) {
  icao = icao.toLowerCase();
  new Content({
    type: "json",
    url: "assets/airlines/"+icao+".json",
    payload: icao.toLowerCase(),
    callback: function(status, data, payload) {
      if(status == "ok") {
        prop.airline.airlines[payload] = new zlsa.atc.Airline(payload, data);
      }
    }
  });
}

function airline_get(icao) {
  icao = icao.toLowerCase();
  return prop.airline.airlines[icao];
}

function airline_ready() {
  for(var i in prop.airline.airlines) {
    prop.airline.airlines[i].validateFleets();
  }
}
<commit_msg>Load Jetstar Asia<commit_after>function airline_init_pre() {
  prop.airline = {};
  prop.airline.airlines = {};
}

/**
 * An aircrcraft operating agency
 */
zlsa.atc.Airline = Fiber.extend(function() {
  return {
    /**
     * Create new airline
     */
    init: function (icao, options) {
      /** ICAO airline designation */
      this.icao = "YYY";

      /** Agency name */
      this.name = "Default airline";

      /** Radio callsign */
      this.callsign = 'Default';

      /** Parameters for flight number generation */
      this.flightNumberGeneration = {
        /** How many characters in the flight number */
        length: 3,
        /** Whether to use alphabetical characters */
        alpha: false,
      };

      /** Named weighted sets of aircraft */
      this.fleets = {
        default: [],
      };

      this.parse(icao, options);
    },

    /**
     * Initialize object from data
     */
    parse: function (icao, data) {
      if (data.icao)
        this.icao = data.icao;
      else
        this.icao = icao;

      this.name = data.name;
      this.callsign = data.callsign.name;
      this.flightNumberGeneration.length = data.callsign.length;
      this.flightNumberGeneration.alpha = (data.callsign.alpha === true);
      if (data.fleets)
        this.fleets = data.fleets;
      else
        this.fleets.default = data.aircraft;

      for (var f in this.fleets) {
        for (var j=0;j<this.fleets[f].length;j++) {
          this.fleets[f][j][0] = this.fleets[f][j][0].toLowerCase();
        }
      }
    },

    /**
     * Return a random ICAO aircraft designator from the given fleet
     *
     * If no fleet is specified the default fleet is used
     */
    chooseAircraft: function (fleet) {
      if (!fleet)
        fleet = 'default';

      try {
        return choose_weight(this.fleets[fleet.toLowerCase()]);
      }
      catch (e) {
        console.log("Unable to find fleet " + fleet
                    + " for airline " + this.icao);
        throw e;
      }
    },

    /**
     * Create a flight number/identifier
     */
    generateFlightNumber: function () {
      var flightNumberLength = this.flightNumberGeneration.length;
      var flightNumber = "";

      var list = "0123456789";

      // Start with a number other than zero
      flightNumber += choose(list.substr(1));

      if (this.flightNumberGeneration.alpha) {
        for (var i=0; i<flightNumberLength - 3; i++)
          flightNumber += choose(list);
        list = "abcdefghijklmnopqrstuvwxyz";
        for (var i=0; i<2; i++)
          flightNumber += choose(list);
      } else {
        for (var i=1; i<flightNumberLength;i++)
          flightNumber += choose(list);
      }
      return flightNumber;
    },

    /**
     * Checks all fleets for valid aircraft identifiers and log errors
     */
    validateFleets: function () {
      for (var f in this.fleets) {
        for (var j=0;j<this.fleets[f].length;j++) {
          if (!(this.fleets[f][j][0] in prop.aircraft.models)) {
            console.warn("Airline " + this.icao.toUpperCase()
                         + " uses nonexistent aircraft " + this.fleets[f][j][0]
                         + ", expect errors");
          }

          if (typeof this.fleets[f][j][1] != "number") {
            console.warn("Airline " + this.icao.toUpperCase()
                         + " uses non numeric weight for aircraft " +
                         this.fleets[f][j][0] + ", expect errors");
          }
        }
      }
    },
  };
});

function airline_init() {
  airline_load("AAL");
  airline_load("ACA");
  airline_load("AEA");
  airline_load("AFL");
  airline_load("AFR");
  airline_load("AIC");
  airline_load("AIRTAXI");
  airline_load("AMX");
  airline_load("ARG");
  airline_load("ASA");
  airline_load("ASQ");
  airline_load("AUA");
  airline_load("AVA");
  airline_load("AWE");
  airline_load("AWI");
  airline_load("AWQ");
  airline_load("AXB");
  airline_load("AZA");
  airline_load("AZU");
  airline_load("BAW");
  airline_load("BCY");
  airline_load("BEE");
  airline_load("BEL");
  airline_load("BER");
  airline_load("BTK");
  airline_load("BWA");
  airline_load("CCA");
  airline_load("CES");
  airline_load("CESSNA");
  airline_load("CFG");
  airline_load("CFS");
  airline_load("CPA");
  airline_load("CPZ");
  airline_load("CSN");
  airline_load("CTV");
  airline_load("CWC");
  airline_load("DAL");
  airline_load("DLH");
  airline_load("EIN");
  airline_load("EMBRAER");
  airline_load("ENY");
  airline_load("ETD");
  airline_load("EXS");
  airline_load("EVA");
  airline_load("EZY");
  airline_load("FAB");
  airline_load("FASTGA");
  airline_load("FDB");
  airline_load("FDX");
  airline_load("FFT");
  airline_load("FLG");
  airline_load("FWI");
  airline_load("GIA");
  airline_load("GJS");
  airline_load("GLO");
  airline_load("GOW");
  airline_load("GWI");
  airline_load("HAL");
  airline_load("HDA");
  airline_load("IAD");
  airline_load("IBE");
  airline_load("IGO");
  airline_load("INC");
  airline_load("JAI");
  airline_load("JAL");
  airline_load("JBU");
  airline_load("JIA");
  airline_load("JSA");
  airline_load("KAL");
  airline_load("KAP");
  airline_load("KLC");
  airline_load("KLM");
  airline_load("LAN");
  airline_load("LIGHTGA");
  airline_load("LLR");
  airline_load("LNI");
  airline_load("MON");
  airline_load("MOV");
  airline_load("NAX");
  airline_load("NKS");
  airline_load("ONE");
  airline_load("PAA");
  airline_load("PDT");
  airline_load("QFA");
  airline_load("QXE");
  airline_load("RFF");
  airline_load("RLU");
  airline_load("RPA");
  airline_load("RYR");
  airline_load("SAS");
  airline_load("SBI");
  airline_load("SCO");
  airline_load("SCX");
  airline_load("SEJ");
  airline_load("SIA");
  airline_load("SJY");
  airline_load("SKW");
  airline_load("SLK");
  airline_load("SVR");
  airline_load("SWA");
  airline_load("SWR");
  airline_load("SXD");
  airline_load("TAM");
  airline_load("TCF");
  airline_load("TCX");
  airline_load("TGw");
  airline_load("THY");
  airline_load("TOM");
  airline_load("TSO");
  airline_load("TUI");
  airline_load("TWA");
  airline_load("TYA");
  airline_load("UAE");
  airline_load("UAL");
  airline_load("UPS");
  airline_load("USAF");
  airline_load("VIR");
  airline_load("VRD");
  airline_load("VTI");
  airline_load("WIA");
  airline_load("XAX");
}

function airline_load(icao) {
  icao = icao.toLowerCase();
  new Content({
    type: "json",
    url: "assets/airlines/"+icao+".json",
    payload: icao.toLowerCase(),
    callback: function(status, data, payload) {
      if(status == "ok") {
        prop.airline.airlines[payload] = new zlsa.atc.Airline(payload, data);
      }
    }
  });
}

function airline_get(icao) {
  icao = icao.toLowerCase();
  return prop.airline.airlines[icao];
}

function airline_ready() {
  for(var i in prop.airline.airlines) {
    prop.airline.airlines[i].validateFleets();
  }
}
<|endoftext|>


<commit_before>
var http        = require( 'http'           );
var m3u8        = require( 'm3u8'           );
var fs          = require( 'fs'             );
var ffmpeg      = require( 'ffmpeg'         );
var express     = require( 'express'        );
var url         = require( 'url'            );
var qstring     = require( 'querystring'    );
var cutter      = require( './lib/clipFile' );
var ffprobe     = require( 'node-ffprobe'   );
var p           = require( 'path'           );

var parser;
var file;
var publicDir = p.join(__dirname, "public");
var streamDir = p.join(publicDir, "streams");
var adDir = p.join(publicDir, "ads");

var app = express();

app.use(express.static(publicDir));

function manifestDuration(manifestfile,callback)
{
	parser = m3u8.createStream();
	file   = fs.createReadStream(manifestfile);

	file.pipe(parser);

	total = 0.0;

	parser.on('item', function(item) {
		total += item.get('duration') ;
	});
	parser.on('m3u', function(m3u) {
		callback(total);
	});

}

app.set('view engine', 'jade');

app.get('/', function (req, res)
{

	var path = url.parse(req.url).pathname ;
	var params = qstring.parse(url.parse(req.url).query);

	fs.readdir(streamDir, function(err, data){
		res.render('FileSelector', {"files": data});
	});

});

function cut_file_name( filename, callback )
{
	var path = p.dirname(filename);
	var ext = p.extname(filename);
	var fn = p.basename(filename, ext);
	callback( path, fn, ext );
}

app.get('/cutpoint', function (req,res)
{

	var path = url.parse(req.url).pathname ;
	var params = qstring.parse(url.parse(req.url).query);

	var media = params.media;
	var filename = fs.readdirSync(p.join(streamDir,media)).filter(function(f) {return f && f.match(/.*m3u8$/);})[0] ;
	filename = p.join(streamDir, media, filename);

	manifestDuration(filename,function (total) {

		cut_file_name( filename, function( pth, fn, ext ) {

			var newfile = p.join(pth,fn + "_new" + ext);

			res.render(
				'Parameters',
				{ ogig_file : filename,
				  new_file : newfile,
				  duration : total } );

		} );

	} );

});

app.get('/docut', function (req,res)
{
	console.log(__dirname);

	var path = url.parse(req.url).pathname ;
	var params = qstring.parse(url.parse(req.url).query);

	var old_fn = params['oldfile'];
	var currentStreamDir = p.dirname(old_fn);
	var new_fn = params['newfile'];
	var clpp = params['clippoint'];
	//var ad = params['ad'];
	var ad = "public/ads/ad.ts";
	var dur = params['dur'];

	cutter.clipFile( old_fn, clpp, function(ftc,wtc)
	{
		ftc = p.join(currentStreamDir,ftc);
		var cut1 = p.join(currentStreamDir, p.basename(ftc,p.extname(ftc)) + "_1" + p.extname(ftc));
		var cut2 = p.join(currentStreamDir, p.basename(ftc,p.extname(ftc)) + "_2" + p.extname(ftc));

		//var addur  = 10.0;

		ffprobe( ad, function(err, probeData) {
			var addur = probeData.format.duration;
			console.log(addur);
			cutter.stitchBack(old_fn,new_fn,clpp,ftc,cut1,cut2,ad,addur, cutter.cutMediaFile(res));
		});

		//cutter.stitchBack(old_fn,new_fn,clpp,ftc,cut1,cut2,ad,0, cutter.cutMediaFile(res));

	});
});

function list_param_page( connection, args, callback )
{
	callback(str);
}

var server = app.listen(8080, function ()
{
	console.log('ffmpeg test app listening at http://%s:%s', 'localhost', server.address().port);
});

<commit_msg>Remove cut_file_name<commit_after>
var http        = require( 'http'           );
var m3u8        = require( 'm3u8'           );
var fs          = require( 'fs'             );
var ffmpeg      = require( 'ffmpeg'         );
var express     = require( 'express'        );
var url         = require( 'url'            );
var qstring     = require( 'querystring'    );
var cutter      = require( './lib/clipFile' );
var ffprobe     = require( 'node-ffprobe'   );
var p           = require( 'path'           );

var parser;
var file;
var publicDir = p.join(__dirname, "public");
var streamDir = p.join(publicDir, "streams");
var adDir = p.join(publicDir, "ads");

var app = express();

app.use(express.static(publicDir));

function manifestDuration(manifestfile,callback)
{
	parser = m3u8.createStream();
	file   = fs.createReadStream(manifestfile);

	file.pipe(parser);

	total = 0.0;

	parser.on('item', function(item) {
		total += item.get('duration') ;
	});
	parser.on('m3u', function(m3u) {
		callback(total);
	});

}

app.set('view engine', 'jade');

app.get('/', function (req, res)
{

	var path = url.parse(req.url).pathname ;
	var params = qstring.parse(url.parse(req.url).query);

	fs.readdir(streamDir, function(err, data){
		res.render('FileSelector', {"files": data});
	});

});

app.get('/cutpoint', function (req,res)
{

	var path = url.parse(req.url).pathname ;
	var params = qstring.parse(url.parse(req.url).query);

	var media = params.media;
	filename = p.join(streamDir, media, media + ".m3u8");

	manifestDuration(filename,function (total) {
		var ext = p.extname(filename);
		var newfile = p.join(p.dirname(filename), p.basename(filename, ext) + "_new" + ext);
		res.render(
			'Parameters',
			{ ogig_file : filename,
				new_file : newfile,
			duration : total } );
	} );
});

app.get('/docut', function (req,res)
{
	console.log(__dirname);

	var path = url.parse(req.url).pathname ;
	var params = qstring.parse(url.parse(req.url).query);

	var old_fn = params['oldfile'];
	var currentStreamDir = p.dirname(old_fn);
	var new_fn = params['newfile'];
	var clpp = params['clippoint'];
	//var ad = params['ad'];
	var ad = "public/ads/ad.ts";
	var dur = params['dur'];

	cutter.clipFile( old_fn, clpp, function(ftc,wtc)
	{
		ftc = p.join(currentStreamDir,ftc);
		var cut1 = p.join(currentStreamDir, p.basename(ftc,p.extname(ftc)) + "_1" + p.extname(ftc));
		var cut2 = p.join(currentStreamDir, p.basename(ftc,p.extname(ftc)) + "_2" + p.extname(ftc));

		//var addur  = 10.0;

		ffprobe( ad, function(err, probeData) {
			var addur = probeData.format.duration;
			console.log(addur);
			cutter.stitchBack(old_fn,new_fn,clpp,ftc,cut1,cut2,ad,addur, cutter.cutMediaFile(res));
		});

		//cutter.stitchBack(old_fn,new_fn,clpp,ftc,cut1,cut2,ad,0, cutter.cutMediaFile(res));

	});
});

function list_param_page( connection, args, callback )
{
	callback(str);
}

var server = app.listen(8080, function ()
{
	console.log('ffmpeg test app listening at http://%s:%s', 'localhost', server.address().port);
});

<|endoftext|>


<commit_before>Object.assign(pc, (function () {
    'use strict';

    /**
     *
     * @constructor
     * @name pc.CurveIterator
     * @classdesc CurveIterator performs fast evaluation of a curve by caching
     * knot information where possible.
     * @description Creates a new curve iterator.
     * @param {Curve} curve The curve to iterator over
     * @param {Number} time The time to start iteration, defaults to 0.
     */
    var CurveIterator = function (curve, time) {
        this.curve = curve;
        this.time_ = time || 0;
        this.left = -Infinity;
        this.right = Infinity;
        this.recip = 0;
        this.p0 = 0;
        this.p1 = 0;
        this.m0 = 0;
        this.m1 = 0;

        this.reset(this.time_);
    };

    Object.assign(CurveIterator.prototype, {
        /**
         * @function
         * @name pc.CurveIterator#reset
         * @description Reset the iterator to the specified time
         * @param {Number} time The new time for the iterator
         */
        reset: function (time) {
            var keys = this.curve.keys;
            var len = keys.length;

            this.time_ = time;
            if (!len) {
                // curve is empty
                this.left = -Infinity;
                this.right = Infinity;
                this.recip = 0;
                this.p0 = this.p1 = this.m0 = this.m1 = 0;
            } else {
                if (this.time_ < keys[0][0]) {
                    // iterator falls to the left of the start of the curve
                    this.left = -Infinity;
                    this.right = keys[0][0];
                    this.recip = 0;
                    this.p0 = this.p1 = keys[0][1];
                    this.m0 = this.m1 = 0;
                } else if (this.time_ >= keys[len - 1][0]) {
                    // iterator falls to the right of the end of the curve
                    this.left = keys[len - 1][0];
                    this.right = Infinity;
                    this.recip = 0;
                    this.p0 = this.p1 = keys[len - 1][1];
                    this.m0 = this.m1 = 0;
                } else {
                    // iterator falls within the bounds of the curve
                    // perform a linear search for the key just the to left of the
                    // current time.
                    // (TODO: for cases where the curve has more than 'n' keys it will
                    // be more efficient to perform a binary search here instead. Which is
                    // straight forward thanks to the sorted list of knots).
                    var index = 0;
                    while (this.time_ > keys[index + 1][0]) {
                        index++;
                    }
                    this.left = keys[index][0];
                    this.right = keys[index + 1][0];
                    this.recip = 1.0 / (this.right - this.left);
                    this.p0 = keys[index][1];
                    this.p1 = keys[index + 1][1];
                    if (this._isHermite()) {
                        this._calcTangents(keys, index);
                    }
                }
            }
        },

        // returns true if the curve is a hermite and false otherwise
        _isHermite: function () {
            return [pc.CURVE_CATMULL,
                pc.CURVE_CARDINAL,
                pc.CURVE_CARDINAL_STABLE].indexOf(this.curve.type) != -1;
        },

        // calculate tangents for the hermite curve
        _calcTangents: function (keys, index) {
            var a;
            var b = keys[index];
            var c = keys[index + 1];
            var d;

            if (index === 0) {
                a = [keys[0][0] + (keys[0][0] - keys[1][0]),
                    keys[0][1] + (keys[0][1] - keys[1][1])];
            } else {
                a = keys[index - 1];
            }

            if (index == keys.length - 2) {
                d = [keys[index + 1][0] + (keys[index + 1][0] - keys[index][0]),
                    keys[index + 1][1] + (keys[index + 1][1] - keys[index][1])];
            } else {
                d = keys[index + 2];
            }

            // tension
            var tension = this.curve.tension;

            if (this.curve.type === pc.CURVE_CARDINAL_STABLE) {
                // tangent scale (due to non-uniform knot spacing)
                var s1_ = 2 * (c[0] - b[0]) / (c[0] - a[0]);
                var s2_ = 2 * (c[0] - b[0]) / (d[0] - b[0]);

                this.m0 = tension * s1_ * (c[1] - a[1]);
                this.m1 = tension * s2_ * (d[1] - b[1]);
            } else {
                if (this.curve.type === pc.CURVE_CATMULL) {
                    tension = 0.5;
                }

                // original cardinal tangent calc
                var s1 = (c[0] - b[0]) / (b[0] - a[0]);
                var s2 = (c[0] - b[0]) / (d[0] - c[0]);

                var a_ = b[1] + (a[1] - b[1]) * s1;
                var d_ = c[1] + (d[1] - c[1]) * s2;

                this.m0 = tension * (c[1] - a_);
                this.m1 = tension * (d_ - b[1]);
            }
        },

        _evaluateHermite: function (p0, p1, t0, t1, s) {
            var s2 = s * s;
            var s3 = s2 * s;
            var h0 = 2 * s3 - 3 * s2 + 1;
            var h1 = -2 * s3 + 3 * s2;
            var h2 = s3 - 2 * s2 + s;
            var h3 = s3 - s2;
            return p0 * h0 + p1 * h1 + t0 * h2 + t1 * h3;
        },

        /**
         * @function
         * @name pc.CurveIterator#time
         * @description Get the current iterator time
         * @returns {Number} The current time.
         */
        time: function () {
            return this.time_;
        },

        /**
         * @function
         * @name pc.CurveIterator#evaluate
         * @description Evaluate the curve at the current time
         * @returns {Number} The curve value at the current time
         */
        evaluate: function () {
            var curve = this.curve;

            var result;
            if (curve.type === pc.CURVE_STEP) {
                // step
                result = this.p0;
            } else {
                // calculate normalized t
                var t = 0;
                if (this.recip !== 0) {
                    t = (this.time_ - this.left) * this.recip;
                }

                if (curve.type === pc.CURVE_LINEAR) {
                    // linear
                    result = pc.math.lerp(this.p0, this.p1, t);
                } else if (curve.type === pc.CURVE_SMOOTHSTEP) {
                    // smoothstep
                    result = pc.math.lerp(this.p0, this.p1, t * t * (3 - 2 * t));
                } else {
                    // curve
                    result = this._evaluateHermite(this.p0, this.p1, this.m0, this.m1, t);
                }
            }
            return result;
        },

        /**
         * @function
         * @name pc.CurveIterator#advance
         * @description Advance the iterator by the passed in amount
         * @param {Number} amount The amount of time to advance the iterator
         */
        advance: function (amount) {
            this.time_ += amount;

            if (amount >= 0) {
                if (this.time_ > this.right) {
                    this.reset(this.time_);
                }
            } else if (this.time_ < this.left) {
                this.reset(this.time_);
            }
        },

        /**
         * @function
         * @name pc.CurveIterator.value
         * @description Evaluate the curve at the given time
         * @param {Number} time The time at which to evaluate the curve
         * @returns {Number} The curve value at the given time
         */
        value: function (time) {
            if (time < this.left || time > this.right) {
                this.reset(time);
            } else {
                this.time_ = time;
            }
            return this.evaluate();
        }
    });

    return {
        CurveIterator: CurveIterator
    };
}()));
<commit_msg>added tests for infinite in iterator<commit_after>Object.assign(pc, (function () {
    'use strict';

    /**
     *
     * @constructor
     * @name pc.CurveIterator
     * @classdesc CurveIterator performs fast evaluation of a curve by caching
     * knot information where possible.
     * @description Creates a new curve iterator.
     * @param {Curve} curve The curve to iterator over
     * @param {Number} time The time to start iteration, defaults to 0.
     */
    var CurveIterator = function (curve, time) {
        this.curve = curve;
        this.time_ = time || 0;
        this.left = -Infinity;
        this.right = Infinity;
        this.recip = 0;
        this.p0 = 0;
        this.p1 = 0;
        this.m0 = 0;
        this.m1 = 0;

        this.reset(this.time_);
    };

    Object.assign(CurveIterator.prototype, {
        /**
         * @function
         * @name pc.CurveIterator#reset
         * @description Reset the iterator to the specified time
         * @param {Number} time The new time for the iterator
         */
        reset: function (time) {
            var keys = this.curve.keys;
            var len = keys.length;

            this.time_ = time;
            if (!len) {
                // curve is empty
                this.left = -Infinity;
                this.right = Infinity;
                this.recip = 0;
                this.p0 = this.p1 = this.m0 = this.m1 = 0;
            } else {
                if (this.time_ < keys[0][0]) {
                    // iterator falls to the left of the start of the curve
                    this.left = -Infinity;
                    this.right = keys[0][0];
                    this.recip = 0;
                    this.p0 = this.p1 = keys[0][1];
                    this.m0 = this.m1 = 0;
                } else if (this.time_ >= keys[len - 1][0]) {
                    // iterator falls to the right of the end of the curve
                    this.left = keys[len - 1][0];
                    this.right = Infinity;
                    this.recip = 0;
                    this.p0 = this.p1 = keys[len - 1][1];
                    this.m0 = this.m1 = 0;
                } else {
                    // iterator falls within the bounds of the curve
                    // perform a linear search for the key just the to left of the
                    // current time.
                    // (TODO: for cases where the curve has more than 'n' keys it will
                    // be more efficient to perform a binary search here instead. Which is
                    // straight forward thanks to the sorted list of knots).
                    var index = 0;
                    while (this.time_ > keys[index + 1][0]) {
                        index++;
                    }
                    this.left = keys[index][0];
                    this.right = keys[index + 1][0];
                    var diff = 1.0 / (this.right - this.left);
                    this.recip = (isFinite(diff) ? diff : 0);
                    this.p0 = keys[index][1];
                    this.p1 = keys[index + 1][1];
                    if (this._isHermite()) {
                        this._calcTangents(keys, index);
                    }
                }
            }
        },

        // returns true if the curve is a hermite and false otherwise
        _isHermite: function () {
            return [pc.CURVE_CATMULL,
                pc.CURVE_CARDINAL,
                pc.CURVE_CARDINAL_STABLE].indexOf(this.curve.type) != -1;
        },

        // calculate tangents for the hermite curve
        _calcTangents: function (keys, index) {
            var a;
            var b = keys[index];
            var c = keys[index + 1];
            var d;

            if (index === 0) {
                a = [keys[0][0] + (keys[0][0] - keys[1][0]),
                    keys[0][1] + (keys[0][1] - keys[1][1])];
            } else {
                a = keys[index - 1];
            }

            if (index == keys.length - 2) {
                d = [keys[index + 1][0] + (keys[index + 1][0] - keys[index][0]),
                    keys[index + 1][1] + (keys[index + 1][1] - keys[index][1])];
            } else {
                d = keys[index + 2];
            }

            // tension
            var tension = this.curve.tension;

            if (this.curve.type === pc.CURVE_CARDINAL_STABLE) {
                // tangent scale (due to non-uniform knot spacing)
                var s1_ = 2 * (c[0] - b[0]) / (c[0] - a[0]);
                var s2_ = 2 * (c[0] - b[0]) / (d[0] - b[0]);

                this.m0 = tension * (isFinite(s1_) ? s1_ : 0) * (c[1] - a[1]);
                this.m1 = tension * (isFinite(s2_) ? s2_ : 0) * (d[1] - b[1]);
            } else {
                if (this.curve.type === pc.CURVE_CATMULL) {
                    tension = 0.5;
                }

                // original cardinal tangent calc
                var s1 = (c[0] - b[0]) / (b[0] - a[0]);
                var s2 = (c[0] - b[0]) / (d[0] - c[0]);

                var a_ = b[1] + (a[1] - b[1]) * (isFinite(s1) ? s1 : 0);
                var d_ = c[1] + (d[1] - c[1]) * (isFinite(s2) ? s2 : 0);

                this.m0 = tension * (c[1] - a_);
                this.m1 = tension * (d_ - b[1]);
            }
        },

        _evaluateHermite: function (p0, p1, t0, t1, s) {
            var s2 = s * s;
            var s3 = s2 * s;
            var h0 = 2 * s3 - 3 * s2 + 1;
            var h1 = -2 * s3 + 3 * s2;
            var h2 = s3 - 2 * s2 + s;
            var h3 = s3 - s2;
            return p0 * h0 + p1 * h1 + t0 * h2 + t1 * h3;
        },

        /**
         * @function
         * @name pc.CurveIterator#time
         * @description Get the current iterator time
         * @returns {Number} The current time.
         */
        time: function () {
            return this.time_;
        },

        /**
         * @function
         * @name pc.CurveIterator#evaluate
         * @description Evaluate the curve at the current time
         * @returns {Number} The curve value at the current time
         */
        evaluate: function () {
            var curve = this.curve;

            var result;
            if (curve.type === pc.CURVE_STEP) {
                // step
                result = this.p0;
            } else {
                // calculate normalized t
                var t = 0;
                if (this.recip !== 0) {
                    t = (this.time_ - this.left) * this.recip;
                }
                if (curve.type === pc.CURVE_LINEAR) {
                    // linear
                    result = pc.math.lerp(this.p0, this.p1, t);
                } else if (curve.type === pc.CURVE_SMOOTHSTEP) {
                    // smoothstep
                    result = pc.math.lerp(this.p0, this.p1, t * t * (3 - 2 * t));
                } else {
                    // curve
                    result = this._evaluateHermite(this.p0, this.p1, this.m0, this.m1, t);
                }
            }
            return result;
        },

        /**
         * @function
         * @name pc.CurveIterator#advance
         * @description Advance the iterator by the passed in amount
         * @param {Number} amount The amount of time to advance the iterator
         */
        advance: function (amount) {
            this.time_ += amount;

            if (amount >= 0) {
                if (this.time_ > this.right) {
                    this.reset(this.time_);
                }
            } else if (this.time_ < this.left) {
                this.reset(this.time_);
            }
        },

        /**
         * @function
         * @name pc.CurveIterator.value
         * @description Evaluate the curve at the given time
         * @param {Number} time The time at which to evaluate the curve
         * @returns {Number} The curve value at the given time
         */
        value: function (time) {
            if (time < this.left || time > this.right) {
                this.reset(time);
            } else {
                this.time_ = time;
            }
            return this.evaluate();
        }
    });

    return {
        CurveIterator: CurveIterator
    };
}()));
<|endoftext|>


<commit_before>const readdirSync = require('fs').readdirSync;
const fileExists = require('roc').fileExists;

const packages =
    !fileExists(`${process.cwd()}/packages`) ? [] :
    readdirSync(`${process.cwd()}/packages`)
    .map((package) => {
        if (fileExists(`${process.cwd()}/packages/${package}/package.json`)) {
            return {
                folder: package,
                path: `${process.cwd()}/packages/${package}`,
                name: require(`${process.cwd()}/packages/${package}/package.json`).name
            }
        }
    })
    .filter((package) => package !== undefined);

module.exports = {
    commands: {
        'build': require('../commands/build')(packages),
        'build:watch': require('../commands/build-watch')(packages),
        'clean': require('../commands/clean')(packages),
        'docs': require('../commands/docs')(packages),
        'esdocs': require('../commands/esdocs')(packages),
        'link': require('../commands/link')(packages),
        'lint:alias': require('../commands/lint-alias')(packages),
        'lint': require('../commands/lint')(packages),
        'release': require('../commands/release')(packages)
    }
};
<commit_msg>Fixes regression from 1.3.1<commit_after>const fs = require('fs');
const roc = require('roc');

const folderExists = (path) => {
    path = roc.getAbsolutePath(path);
    try {
        return fs.statSync(path).isDirectory();
    } catch (error) {
        return false;
    }
};

const packages =
    !folderExists(`${process.cwd()}/packages`) ? [] :
    fs.readdirSync(`${process.cwd()}/packages`)
    .map((package) => {
        if (roc.fileExists(`${process.cwd()}/packages/${package}/package.json`)) {
            return {
                folder: package,
                path: `${process.cwd()}/packages/${package}`,
                name: require(`${process.cwd()}/packages/${package}/package.json`).name
            }
        }
    })
    .filter((package) => package !== undefined);

module.exports = {
    commands: {
        'build': require('../commands/build')(packages),
        'build:watch': require('../commands/build-watch')(packages),
        'clean': require('../commands/clean')(packages),
        'docs': require('../commands/docs')(packages),
        'esdocs': require('../commands/esdocs')(packages),
        'link': require('../commands/link')(packages),
        'lint:alias': require('../commands/lint-alias')(packages),
        'lint': require('../commands/lint')(packages),
        'release': require('../commands/release')(packages)
    }
};
<|endoftext|>


<commit_before>(function () {
    'use strict';

    var socket_url,
        web_socket,
        chat_div = document.getElementById('chat-content'),
        chat_input = document.getElementById('chat-input'),
        users = {},
        active_users = [];

    function addMessage(message) {
        chat_div.textContent += message + '\n';
    }

    if (window.location.protocol === 'https:') {
        socket_url = 'wss:';
    } else {
        socket_url = 'ws:';
    }
    socket_url += '//' + window.location.host;
    socket_url += socket_url_path;

    web_socket = new WebSocket(socket_url);
    web_socket.onmessage = function (msg) {
        var obj = JSON.parse(msg.data),
            data = obj['data'];
        if (obj['type'] === 'curstate') {
            var userlist = data['userlist'],
                history = data['history'];
            for (var i in userlist) {
                var user = userlist[i];
                users[user['connection_id']] = {
                    'nickname': user['nickname'],
                    'is_asker': user['is_asker']
                };
            }
            for (var i in history) {
                var userName = users[history[i][0]]['nickname'],
                    message = history[i][1];
                addMessage(userName + ': ' + message);
            }
            active_users.concat(data['onlinelist']);
        } else if (obj['type'] === 'message') {
            var userName = users[data['connection_id']]['nickname'],
                message = data['message'];
            addMessage(userName + ': ' + message);
        } else if (obj['type'] === 'connect') {
            users[data['connection_id']] = {
                'nickname': data['nickname'],
                'is_asker': data['is_asker']
            };
            active_users.push(data['connection_id']);
            addMessage(data['nickname'] + ' connected.');
        } else if (obj['type'] === 'disconnect') {
            var index = active_users.indexOf(data['connection_id']);
            active_users.splice(index, 1);
            addMessage(users[data['connection_id']]['nickname'] + ' disconnected.');
        }
    };

    document.getElementById('chat-input-form').addEventListener('submit', function(e) {
        e.preventDefault();
        var message = chat_input.value;
        chat_input.value = '';
        web_socket.send(JSON.stringify({
            'type': 'message',
            'data': message
        }));
        chat_input.focus();
    });

})();
<commit_msg>update chat to new ui style<commit_after>(function () {
    'use strict';

    var socket_url,
        web_socket,
        chat_div = document.getElementById('chat-content'),
        chat_input = document.getElementById('chat-input'),
        users = {},
        active_users = [];

    function addMessage(type, username, message) {
        var p = document.createElement("p");
        p.className = type;
        var span = document.createElement("span");
        var boldText = username;
        if (type === 'asker' || type === 'responder') {
            boldText += ':';
        }
        span.appendChild(document.createTextNode(boldText));
        p.appendChild(span);
        p.appendChild(document.createTextNode(' ' + message));
        chat_div.appendChild(p);
    }

    if (window.location.protocol === 'https:') {
        socket_url = 'wss:';
    } else {
        socket_url = 'ws:';
    }
    socket_url += '//' + window.location.host;
    socket_url += socket_url_path;

    web_socket = new WebSocket(socket_url);
    web_socket.onmessage = function (msg) {
        var obj = JSON.parse(msg.data),
            data = obj['data'];
        if (obj['type'] === 'curstate') {
            var userlist = data['userlist'],
                history = data['history'];
            for (var i in userlist) {
                var user = userlist[i];
                users[user['connection_id']] = {
                    'nickname': user['nickname'],
                    'is_asker': user['is_asker']
                };
            }
            for (var i in history) {
                var user = users[history[i][0]],
                    message = history[i][1],
                    type = 'responder';
                if (user['is_asker']) {
                    type = 'asker';
                }
                addMessage(type, user['nickname'], message);
            }
            active_users.concat(data['onlinelist']);
        } else if (obj['type'] === 'message') {
            var user = users[data['connection_id']],
                message = data['message'],
                type = 'responder';
            if (user['is_asker']) {
                type = 'asker';
            }
            addMessage(type, user['nickname'], message);
        } else if (obj['type'] === 'connect') {
            users[data['connection_id']] = {
                'nickname': data['nickname'],
                'is_asker': data['is_asker']
            };
            active_users.push(data['connection_id']);
            addMessage('meta', data['nickname'], 'connected.');
        } else if (obj['type'] === 'disconnect') {
            var index = active_users.indexOf(data['connection_id']);
            active_users.splice(index, 1);
            addMessage('meta', users[data['connection_id']]['nickname'], 'disconnected.');
        }
    };

    document.getElementById('chat-input-form').addEventListener('submit', function(e) {
        e.preventDefault();
        var message = chat_input.value;
        chat_input.value = '';
        web_socket.send(JSON.stringify({
            'type': 'message',
            'data': message
        }));
        chat_input.focus();
    });

})();
<|endoftext|>


<commit_before>const _ = require('lodash');

const DatabaseWrapper = require('../components/DatabaseWrapper/DatabaseWrapper');

const databaseWrapper = new DatabaseWrapper();

databaseWrapper.connect();

/**
 * Gets the current online status of the database wrapper, if online calls next otherwise sends
 * a internal server error to the client.
 */
function validateConnectionStatus(req, res, next) {
  if (databaseWrapper.getOnlineStatus()) {
    return next();
  }
  return res.status(503).send({ error: 'Database Connection', description: 'The database service is currently unavailable' });
}

/**
 * Checks with the database to see if the username already exists and throws a bad request otherwise
 * calls next.
 */
function validateUsernameDoesNotExist(req, res, next) {
  const username = req.volunteer.username;

  databaseWrapper.doesUsernameExist(username)
    .then(() => res.status(403).send({ error: 'Username exists', description: `The username ${username} already exists` }))
    .catch(() => next());
}

/**
 * Checks with the database to see if the email already exists and throws a bad request otherwise
 * calls next.
 */
function validateEmailDoesNotExist(req, res, next) {
  const email = req.volunteer.email;

  databaseWrapper.doesEmailExist(email)
    .then(() => res.status(403).send({ error: 'Email exists', description: `The email ${email} already exist` }))
    .catch(() => next());
}

/**
 * Checks with the database to see if the username already exists and calls next otherwise
 * throws a bad request otherwise.
 */
function validateUsernameDoesExist(req, res, next) {
  let username;

  if (!_.isNil(req.params.username)) {
    username = req.params.username;
  } else if (!_.isNil(req.body.username)) {
    username = req.body.username;
  } else if (!_.isNil(req.username)) {
    username = req.username;
  } else {
    res.status(400).send({ error: 'Username validation', description: 'The username parameter was not passed' });
  }

  databaseWrapper.doesUsernameExist(username)
    .then((id) => {
      req.id = id;
      req.username = username;
      next();
    })
    .catch(() => {
      if (!res.headersSent) {
        res.status(400).send({ error: 'Username does not exists', description: `The username ${username} does not exists` });
      }
    });
}

/**
 * Checks with the database to see if the email already exists and calls next otherwise
 * throws a bad request.
 */
function validateEmailDoesExist(req, res, next) {
  const email = req.body.email;

  databaseWrapper.doesEmailExist(email)
    .then(() => next())
    .catch(() => res.status(400).send({ error: 'Email Does not exists', description: `The email ${email} does not exist` }));
}

/**
 * Pulls the users verify code and salt from the verify table. Salts and hashes the passed code
 * with the stored salt. Compares the stored code with the newly salted code and calls next,
 * otherwise throws a 401 invalid authentication.
 */
function validateVerificationCode(req, res, next) {
  res.status(503).send({ error: 'Currently unavailable', description: 'This service is currently unavailable' });
  next();
}

/**
 * Generates a random number and a salt, salts and hashes a number and stores that in the database
 * under the password reset table with the id of the user, the salt and hashed code and the salt
 * that was used to salt and hash the stored code for use when validating the requesting code.
 * Then calls next where the code will be used to generate a link to send to the client which
 * will allow them to to send a reset request with the code and there new password.
 */
function createPasswordResetCode(req, res, next) {
  res.status(503).send({ error: 'Currently unavailable', description: 'This service is currently unavailable' });
  next();
}

module.exports = {
  validateConnectionStatus,
  validateUsernameDoesNotExist,
  validateEmailDoesNotExist,
  validateUsernameDoesExist,
  validateEmailDoesExist,
  validateVerificationCode,
  createPasswordResetCode,
};
<commit_msg>validation of username and email<commit_after>const _ = require('lodash');

const DatabaseWrapper = require('../components/DatabaseWrapper/DatabaseWrapper');

const databaseWrapper = new DatabaseWrapper();

databaseWrapper.connect();

/**
 * Gets the current online status of the database wrapper, if online calls next otherwise sends
 * a internal server error to the client.
 */
function validateConnectionStatus(req, res, next) {
  if (databaseWrapper.getOnlineStatus()) {
    return next();
  }
  return res.status(503).send({ error: 'Database Connection', description: 'The database service is currently unavailable' });
}

/**
 * Checks with the database to see if the username already exists and throws a bad request otherwise
 * calls next.
 */
function validateUsernameDoesNotExist(req, res, next) {
  const username = req.volunteer.username;

  databaseWrapper.doesUsernameExist(username)
    .then(() => res.status(403).send({ error: 'Username exists', description: `The username ${username} already exists` }))
    .catch(() => next());
}

/**
 * Checks with the database to see if the email already exists and throws a bad request otherwise
 * calls next.
 */
function validateEmailDoesNotExist(req, res, next) {
  const email = req.volunteer.email;

  databaseWrapper.doesEmailExist(email)
    .then(() => res.status(403).send({ error: 'Email exists', description: `The email ${email} already exist` }))
    .catch(() => next());
}

/**
 * Checks with the database to see if the username already exists and calls next otherwise
 * throws a bad request otherwise.
 */
function validateUsernameDoesExist(req, res, next) {
  let username;

  if (!_.isNil(req.params.username)) {
    username = req.params.username;
  } else if (!_.isNil(req.body.username)) {
    username = req.body.username;
  } else if (!_.isNil(req.username)) {
    username = req.username;
  } else {
    res.status(400).send({ error: 'Username validation', description: 'The username parameter was not passed' });
  }

  if (!res.headersSent) {
    databaseWrapper.doesUsernameExist(username)
    .then((id) => {
      req.id = id;
      req.username = username;
      next();
    })
    .catch(() => res.status(400).send({ error: 'Username does not exists', description: `The username ${username} does not exists` }));
  }
}

/**
 * Checks with the database to see if the email already exists and calls next otherwise
 * throws a bad request.
 */
function validateEmailDoesExist(req, res, next) {
  let email;

  if (!_.isNil(req.params.email)) {
    email = req.params.email;
  } else if (!_.isNil(req.body.email)) {
    email = req.body.email;
  } else if (!_.isNil(req.email)) {
    email = req.email;
  } else {
    res.status(400).send({ error: 'Email validation', description: 'The email parameter was not passed' });
  }

  if (!res.headersSent) {
    databaseWrapper.doesEmailExist(email)
    .then(() => next())
    .catch(() => res.status(400).send({ error: 'Email Does not exists', description: `The email ${email} does not exist` }));
  }
}

/**
 * Pulls the users verify code and salt from the verify table. Salts and hashes the passed code
 * with the stored salt. Compares the stored code with the newly salted code and calls next,
 * otherwise throws a 401 invalid authentication.
 */
function validateVerificationCode(req, res, next) {
  res.status(503).send({ error: 'Currently unavailable', description: 'This service is currently unavailable' });
  next();
}

/**
 * Generates a random number and a salt, salts and hashes a number and stores that in the database
 * under the password reset table with the id of the user, the salt and hashed code and the salt
 * that was used to salt and hash the stored code for use when validating the requesting code.
 * Then calls next where the code will be used to generate a link to send to the client which
 * will allow them to to send a reset request with the code and there new password.
 */
function createPasswordResetCode(req, res, next) {
  res.status(503).send({ error: 'Currently unavailable', description: 'This service is currently unavailable' });
  next();
}

module.exports = {
  validateConnectionStatus,
  validateUsernameDoesNotExist,
  validateEmailDoesNotExist,
  validateUsernameDoesExist,
  validateEmailDoesExist,
  validateVerificationCode,
  createPasswordResetCode,
};
<|endoftext|>