(function declareConstants () { // initialization const navigator = { platform: 'windows' }; const ffprobePath = (navigator.platform === 'MacIntel') ? '/Users/windz/bin/ffprobe' : 'C:\\myTools\\ffmpeg-20161101-60178e7-win64-static\\bin\\ffprobe.exe'; if (fs.existsSync(ffprobePath)) { setFfprobePath(ffprobePath); canProbeVideo = true; } // CONSTANTS.DB const dataStoreConfig = { filename: __dirname + '/db/fileList.db', autoload: true }; CONSTANTS.DB = new DataStore(dataStoreConfig); // CONSTANTS.IMAGE const IMAGE = CONSTANTS.IMAGE = "i"; // CONSTANTS.IMAGE const VIDEO = CONSTANTS.VIDEO = "v"; // CONSTANTS.EXT_DICT CONSTANTS.EXT_DICT = { "jpg": IMAGE, "jpeg": IMAGE, "png": IMAGE, "bmp": IMAGE, "avi": VIDEO, "mp4": VIDEO, "mkv": VIDEO, "wmv": VIDEO }; // CONSTANTS.RESOLUTION_DICT CONSTANTS.RESOLUTION_DICT = { "HD": "HD", "SD": "SD", "GG": "GG" }; // CONSTANTS.HD_VIDEO_HEIGHT CONSTANTS.HD_VIDEO_HEIGHT = 1000; })();
module.exports = (function () { //轉檔駐列 var ffmpeg = require('fluent-ffmpeg'), app = this, fs = require('fs-extra'), listCom = require('./listCom')(function (_path,_next) { //var mp4Id = Date.now() + ( ( Math.random()*20000 ) >>1 ), var mp4Id = _path.split("/")[1].split(".")[0], wmimage = "logo.png", command = new ffmpeg(_path) //.inputFormat('avi') //.size('720x480').aspect('4:3').autopad(true) //.addOption('-vf', 'movie='+wmimage+ ' [watermark]; [in] [watermark] overlay=main_w-overlay_w-10:10 [out]') //浮水印 .complexFilter([ 'scale=720:480[rescaled]', "movie="+wmimage+"[mm]", { filter: 'overlay', options: { x: "main_w-overlay_w-10", y: 10 }, inputs: ['rescaled', 'mm'], outputs: 'redgreen' } ],'redgreen') .on("start", function() { app.onStart( mp4Id ); //console.log('An start: ' + _path); }) .on('error', function(err) { app.onError( mp4Id, err); //console.log('An error occurred: ' + err.message); _next(); }) .on('progress', function(progress) { //console.log('Processing a : ' + progress.percent + '% done'); app.onProgress( mp4Id, progress.percent ); }) .on("end",function () { fs.rename('upfile/' + mp4Id + '_.mp4', 'upfile/' + mp4Id + '.mp4', function(err) { if (err) throw err; //console.log("end! " + mp4Id); app.onEnd( mp4Id ); _next(); }); }) .save('upfile/' + mp4Id + '_.mp4'); }); ffmpeg.setFfmpegPath("D:/ffmpeg/bin/ffmpeg.exe"); ffmpeg.setFfprobePath("D:/ffmpeg/bin/ffprobe.exe"); this.push = function (_path) { listCom.push(_path); } this.onProgress = function ( _id, _progress ) { // body... console.log('Processing a : '+ _id + " " + _progress + '% done'); } this.onEnd = function (_id) { console.log("end! " + _id); } this.onError = function (_id , err) { console.log("error! " + _id +" "+err.message); } this.onStart = function (_id) { console.log("start! " + _id); } this.chkStep = listCom.chkStep; return this; })();
!function () { // if `nedb` run in render process, it will use browser's storage for the DB. Hence, nedb to declare in main process and use IPC to communicate // the path is useless, electron will save it in browser storage (filename = key to doc saving the whole DB)... // console.log('os:', os.platform()); const dbDirPath = (os.platform() !== 'win32') ? '/Users/windz/Code/a-video-explorer/db' : `${__dirname}\\..\\db`; const dataStoreConfig = { filename: path.join(dbDirPath, 'fileList.db'), autoload: false }; const DB = new DataStore(dataStoreConfig); DB.loadDatabase((err) => { if (err) console.error(err); else console.log('DB successfully loaded~'); }); // create index in DB (_id is automatically indexed) // DB.ensureIndex({ fieldName: '_id', unique: true, sparse: false }, function (err) { // if (err) console.error(err); // else console.log('DB index created'); // }); /*|================================================================|*/ /*| IPC events |*/ /*|================================================================|*/ ipcMain.on('getAll', (event) => { DB.find({}, (err, docs) => { console.log(err, docs); }); }); ipcMain.on('presist', (event) => { DB.persistence.compactDatafile(); }); ipcMain.on('get', (event, id) => { DB.findOne({ _id: id }, (err, doc) => { event.returnValue = { err, doc }; }); }); ipcMain.on('upsert', (event, doc) => { const query = { _id: doc._id }; const options = { upsert: true }; DB.update(query, doc, options, function (err, numAffected, affectedDocuments, upsertFlag) { // affectedDocuments is set, iff, upsertFlag = true or options.returnUpdatedDocs = true event.returnValue = { err }; }); }); ipcMain.on('clearNonExist', (event) => { DB.find({}, (err, docs) => { console.log('--- clearNonExist() start ---'); const paths = docs.map( d => d._id ); paths.forEach((p) => { try { fs.accessSync(p); } catch (err) { console.log(`[path 404]\t ${p}`); DB.remove({ _id: p }, { multi: false }, function (err, numRemoved) { if (numRemoved !== 1) console.error(`[remove 500]\t ${p}`); else console.log(`[remove 200]\t ${p}`); }); } }); }); }); // ffprobe const ffprobePath = (os.platform() !== 'win32') ? '/Users/windz/bin/ffprobe' : 'C:\\myTools\\ffmpeg-20161101-60178e7-win64-static\\bin\\ffprobeeee.exe'; const canProbeVideo = fs.existsSync(ffprobePath); if (canProbeVideo) setFfprobePath(ffprobePath); ipcMain.on('getResolution', (event, filePath) => { function gotError(err) { console.log('getResolution', err); event.returnValue = { err }; } function gotMetadata(shouldSave, stat, metadata) { if (shouldSave) { const doc = { _id: filePath, stat, metadata }; const query = { _id: filePath }; const options = { upsert: true }; DB.update(query, doc, options, function (err, numAffected, affectedDocuments, upsertFlag) { // affectedDocuments is set, iff, upsertFlag = true or options.returnUpdatedDocs = true if (err) return gotError(err); event.returnValue = { metadata }; }); } else event.returnValue = { metadata }; } function readMeta(filePath, stat) { if (canProbeVideo) { ffprobe(filePath, function(err, metadata) { if (err) return gotError(err); gotMetadata(true, stat, metadata); }); } else return gotError(new Error('ffprobe not found')); } // find in DB DB.findOne({ _id: filePath }, (err, doc) => { if (err) return gotError(err); const stat = fs.statSync(filePath); // not found, save and reply if (doc === null) { readMeta(filePath, stat); } else { if (stat.mtime.getTime() !== (new Date(doc.stat.mtime)).getTime()) { readMeta(filePath, stat); } else gotMetadata(false, stat, doc.metadata); } }); }); }();
#!/usr/bin/env node const config = require('./config') const path = require('path') const fse = require('fs-extra') const program = require('commander') const commands = require('./commands') const ffmpeg = require('fluent-ffmpeg') //paths to the ffmpeg binaries for fluent-ffmpeg ffmpeg.setFfmpegPath(config.ffmpegPath) ffmpeg.setFfprobePath(config.ffprobePath) //set up the command line interface with the commander module program .version(config.version) .usage('[options] <videofile ...>') .description('Combines each video files with a cooresponding audio files (which should be in the same folder and differ only in extension).') .option('--output-suffix [string]', 'Suffix for output filenames', '') .option('--output-folder [string]', 'Folder for output filenames [combine]', 'combine') .option('--output-extension [string]', 'Extension for output filenames [.mp4]', '.mp4') .option('--audio-extension [string]', 'Extension for audio files [.m4a]', '.m4a') .option('--audio-suffix [string]', 'Suffix for audio files') .option('--audio-folder [string]', 'Folder for audio files (default is same folder as video files)') .option('-v, --verbose', 'Logs information about execution') .parse(process.argv)
}) .option('ffprobe-path', { describe: 'Location ffprobe executable to use' }) .usage('Usage: $0 --in [originalfile] --out [convertedfile]') .demandOption(['in', 'out']) .argv; var src = argv.in; if (argv.ffmpegPath) { FFmpeg.setFfmpegPath(argv.ffmpegPath); } if (argv.ffprobePath) { FFmpeg.setFfprobePath(argv.ffprobePath); } new FFmpeg({ source: src }).ffprobe(function (err, metadata) { if (err) { throw err; } else if (!metadata) { console.error('Failed reading metadata from video'); process.exit(1); } var fileExt = src.split('.')[src.split('.').length - 1]; var fileFormats = metadata.format.format_name.split(','); var intermediateFormat = fileFormats.indexOf(fileExt) > -1 ? fileExt : fileFormats[0]; var alpha = new FFmpeg({ source: src });
exports.getAudioMetadata = function(filepath, callback){ ffmpeg.setFfprobePath(config.FFPROBE_PATH[os.type()]); ffmpeg.ffprobe(filepath, function(err, metadata){ return callback(err, metadata); }); }
// 将文件夹下的视频文件每隔1s截图一次并保存 // 文件名如 "1-20160117.mkv" // 截图文件夹为名为 "1" // 截图文件名为 "1-" + 所在秒数 + ".png" 如 "1-3601.png" var ffmpeg = require('fluent-ffmpeg'); var async = require('async'); var fs = require('fs'); ffmpeg.setFfmpegPath('ffmpeg-20160322-git-30d1213-win64-static/bin/ffmpeg.exe'); ffmpeg.setFfprobePath('ffmpeg-20160322-git-30d1213-win64-static/bin/ffprobe.exe'); var videoScreenshotPreSecond = function(fileName, videoFilePath, screenshotsDir, callback) { var screenshotCount = 0; var eachScreenshotCount = 100; var waterfallFuncArr = []; var getScreenshotCountFunc = function(cb) { ffmpeg(videoFilePath).ffprobe(0, function(err, data) { if(!!err) { cb(err, null) } else { screenshotCount = parseInt(data.format.duration); cb(null, {screenshotCount: screenshotCount}) } }); }; waterfallFuncArr.push(getScreenshotCountFunc)
'port': 9000 } }) // If user requested help instructions if (argv.h) { printUsage() process.exit(-1) } // Set temporary directory path if (argv.t) require ('../lib/EngineManager').setTemporaryDirectory(argv.t) // Set ffmpeg executables path if (argv.f) { ffmpeg.setFfmpegPath(argv.f + '/ffmpeg') ffmpeg.setFfprobePath(argv.f + '/ffprobe') } routes.setHttpPort(argv.port) /** * Start HTTP server */ const app = express() app.use(morgan('combined')) app.use(express.static('public')) app.get('/version', routes.getVersion) app.get('/info/:magnet', routes.torrentInfo) app.get('/raw/:magnet/:ind', routes.rawFile) app.get('/probe/:magnet/:ind', routes.probe)
;( function () { var FS = require( 'fs' ) var OS = require( 'os' ) var NUM_CPUS = OS.cpus().length var PATH = require( 'path' ) var UTIL = require( 'util' ) var EventEmitter = require( 'events' ).EventEmitter var EJS = require( 'ejs' ) var CHOKIDAR = require( 'chokidar' ) // use nbqueue to limit the number of async functions var Queue = require( 'nbqueue' ) var QUEUE_SCAN = new Queue( NUM_CPUS ) // resort to filename mime detection because node-webkit can't run mmmagic... var MIME = require( 'mime' ) var FFMPEG = require( 'fluent-ffmpeg' ) var ROOT_DIR = PATH.join( __dirname, '../', '../' ) var VIEWS_DIR = PATH.join( ROOT_DIR, 'views' ) var VIEWS = require( VIEWS_DIR ) var WIN var AUDIO_REGEX = /^audio\/(?:(?!x\-mpegurl).*)$/ var VIDEO_REGEX = /^video\// var SPAWN = require( 'child_process' ).spawn var MPLAYER_PATH var MPLAYER_PROCESS var MPLAYER_ANSWER_REGEX = /^ANS_([\w\d_-]+)=([\w\d _-]+)/ // audio element var AUDIO // set ffmpeg/ffprobe path if windows, // since windows users will more than // likely not have this setup if ( process.platform === 'win32' ) { MPLAYER_PATH = PATH.join( ROOT_DIR, 'include', 'mplayer', 'mplayer.exe' ) FFMPEG.setFfmpegPath( PATH.join( ROOT_DIR, 'include', 'ffmpeg', 'bin', 'ffmpeg.exe' ) ) FFMPEG.setFfprobePath( PATH.join( ROOT_DIR, 'include', 'ffmpeg', 'bin', 'ffprobe.exe' ) ) } else { MPLAYER_PATH = 'mplayer' } process.on( 'uncaughtException', function ( e ) { console.log( e ) console.trace() //process.exit( 1 ) } ) process.on( 'exit', function () { if ( MPLAYER_PROCESS ) { MPLAYER_PROCESS.kill( 'SIGKILL' ) } } ) // clamp a number between min and max function _clamp( target, min, max ) { return target > min ? target < max ? target : max : min } function doError( str ) { Server_log.call( this, 'error', str ) } function doWarning( str ) { Server_log.call( this, 'warning', str ) } function doMessage( str ) { Server_log.call( this, 'message', str ) } /** @constructor */ function Server() { // initialize EventEmitter on this instance EventEmitter.call( this ) var that = this this.nowPlaying = null // define read-only isPlaying property Object.defineProperty( this, 'isPlaying', { get: function () { return !!MPLAYER_PROCESS } } ) var myPaused = false Object.defineProperty( this, 'isPaused', { get: function () { return myPaused }, set: function ( newValue ) { if ( newValue !== myPaused ) { if ( myPaused = newValue ) { that.emit( 'pause' ) } else { that.emit( 'unpause' ) } } } } ) var myVolume = 80; // volume: 0-100 float // use get/set accessor so the new value is always clamped Object.defineProperty( this, 'volume', { get: function () { return myVolume }, set: function ( newValue ) { // clamp to be safe myVolume = _clamp( parseFloat( newValue ), 0, 100 ) } } ) var mySeekPercent = 0; // seekPercent: 0-100 float // use get/set accessor so the new value is always clamped Object.defineProperty( this, 'seekPercent', { get: function () { return mySeekPercent }, set: function ( newValue ) { // clamp to be safe mySeekPercent = _clamp( parseFloat( newValue ), 0, 100 ) } } ) this .on( 'newlibraryfolder', function ( data ) { Server_watchFolder.call( that, data.path ) Server_scanFolder.call( that, data.path ) } ) .on( 'listening', function () { // console.log( 'listening' ) } ) .on( 'connected', function () { Server_init.call( that ) } ) .on( 'stopped', function () { that.isPaused = false } ) .on( 'playing', function () { that.isPaused = false } ) } // inherit from EventEmitter constructor UTIL.inherits( Server, EventEmitter ) Server.prototype.kill = function () { process.exit( 1 ) } // render ejs file Server.prototype.renderFile = function ( name, data ) { if ( !VIEWS[ name ] ) { doError( 'tried to render file \'' + name + '\'.ejs, which does not exist' ) return } var finalData = {} for ( var key in data ) { finalData[ key ] = data[ key ] } finalData.filename = PATH.join( VIEWS_DIR, name + '.ejs' ) return EJS.render( VIEWS[ name ], finalData ) } Server.prototype.playSong = function ( path ) { var that = this // check for existing process if ( MPLAYER_PROCESS ) { // kill it with SIGTERM signal MPLAYER_PROCESS.kill( 'SIGKILL' ) } // create a new mplayer process MPLAYER_PROCESS = SPAWN( MPLAYER_PATH, [ '-slave', '-quiet', '-input', 'nodefault-bindings', '-noconfig', 'all', path //PATH.relative( __dirname, path ) ], { detached: false, stdio: 'pipe' } ) MPLAYER_PROCESS // listen for close event .on( 'close', function ( code, signal ) { // if no signal sent, unset MPLAYER_PROCESS if ( !signal ) { MPLAYER_PROCESS = undefined that.emit( 'songfinished' ) } // clear timeout just in case clearTimeout( mplayerTimeout ) } ) this.emit( 'playing', path ) //var myLength //var myPercent = 0 MPLAYER_PROCESS.stdout .on( 'data', function ( chunk ) { var str = chunk.toString() // parse title and stuff from str or pull from db? var matches = str.match( MPLAYER_ANSWER_REGEX ) if ( matches ) { var m1 = matches[ 1 ].toLowerCase() var m2 = matches[ 2 ] // process stuff before emitting event switch ( m1 ) { case 'pause' : if ( !( that.isPaused = m2 === 'yes' ) ) { initTimeout() } break case 'percent_position' : var f = parseFloat( m2 ) // if value hasn't changed, return and don't emit event if ( Math.abs( f - that.seekPercent ) < 1 ) //if ( f <= that.seekPercent ) { return } that.seekPercent = f break } that.emit( 'mplayer', { key: m1, value: m2 } ) } } ) MPLAYER_PROCESS.stdin.write( 'pausing_keep_force get_time_length\n' ) var mplayerTimeout var initTimeout = function () { mplayerTimeout = setTimeout( function () { if ( !MPLAYER_PROCESS || that.isPaused ) { return } MPLAYER_PROCESS.stdin.write( 'pausing_keep_force get_percent_pos\n' ) initTimeout() }, 250 ) } initTimeout() } Server.prototype.seekToPercent = function ( percent ) { // make sure process exists if ( !MPLAYER_PROCESS ) { return } // defaults to 0 percent = parseFloat( typeof percent !== 'undefined' ? percent : 0 ) // clamps to 0-100 this.seekPercent = percent // seek by percent MPLAYER_PROCESS.stdin.write( 'pausing_keep_force seek ' + this.seekPercent + ' 1' ) } // toggles pause on the currently playing song Server.prototype.pauseSong = function () { // return if there is no mplayer process if ( !MPLAYER_PROCESS ) { return } // pause the currently playing song MPLAYER_PROCESS.stdin.write( 'pause\n' ) MPLAYER_PROCESS.stdin.write( 'pausing_keep_force get_property pause\n' ) this.emit( 'paused' ) } Server.prototype.scanLibrary = function ( folders ) { if ( !folders || !Array.isArray( folders ) ) { return // folders must be an array } var that = this folders.forEach( function ( obj ) { // obj = { path: full path to directory, mtime: last modified time } FS.stat( obj.path, function ( err, stats ) { if ( err ) { return console.log( err ) } if ( !stats.isDirectory() ) { return doWarning.call( that, 'could not scan \'' + obj.path + '\' because it is not a directory' ) } Server_watchFolder.call( that, obj.path ) Server_scanFolder.call( that, obj.path ) } ) } ) } function Server_watchFolder( pathToFolder ) { var that = this // watch with chokidar - uses a lot of memory, might want to experiment with other methods var watcher = CHOKIDAR.watch( pathToFolder, { ignored: /[\/\\]\./, persistent: true, // not sure if this should be set to false or not usePolling: false } ) watcher .on( 'error', function ( err ) { console.log( err ) } ) .on( 'add', function ( path ) { // stats from chokidar were not set on change, so call stat here FS.stat( path, function ( err, stats ) { if ( err ) { return console.log( err ) } Server_scanFile.call( that, PATH.basename( path ), path, stats ) } ) } ) .on( 'change', function ( path ) { // stats from chokidar were not set on change, so call stat here FS.stat( path, function ( err, stats ) { if ( err ) { return console.log( err ) } if ( stats.isDirectory() ) { Server_scanFolder.call( that, path ) return // directory } else { // assume file Server_scanFile.call( that, PATH.basename( path ), path, stats ) } } ) } ) .on( 'addDir', function ( path ) { Server_scanFolder.call( that, path ) // watch folder? } ) .on( 'unlink', function ( path ) { // remove file } ) .on( 'unlinkDir', function ( path ) { // remove folder } ) } function Server_scanFolder( path ) { // make sure path exists if ( !path ) { return } // reference this to use later var that = this FS.readdir( path, function ( err, files ) { if ( err ) { return console.log( err ) } if ( !files ) { return } // // WATCH THIS DIRECTORY HERE? // files.forEach( function ( file ) { var filePath = PATH.join( path, file ) FS.stat( filePath, function ( err, stats ) { if ( err ) { return // console.log( err ) } // recursive scanning - // run this method on the file if it's a directory if ( stats.isDirectory() ) { return Server_scanFolder.call( that, filePath ) } Server_scanFile.call( that, file, filePath, stats ) } ) } ) } ) } // not really needed in mpserver anymore // converts seconds to formatted time - h:mm:ss /* function convertTime( seconds ) { seconds = parseInt( seconds ) var hours = Math.floor( seconds / ( 60 * 60 ) ) var minutes = Math.floor( seconds / 60 ) - ( hours * ( 60 * 60 ) ) seconds = seconds - ( minutes * 60 ) var ret = '' if ( hours !== 0 ) { ret += hours.toString() + ':' if ( minutes.toString().length === 1 ) { minutes = '0' + minutes.toString() } } ret += minutes.toString() + ':' if ( seconds.toString().length === 1 ) { seconds = '0' + seconds.toString() } ret += seconds.toString() return ret } */ function Server_scanFile( fileName, filePath, stats ) { if ( !filePath ) { return } // return if hidden file if ( fileName.charAt( 0 ) === '.' ) { return } var that = this var mime = MIME.lookup( filePath ) if ( AUDIO_REGEX.test( mime ) ) { // check indexeddb for existing file before scanning var transaction = WIN.mpdb.transaction( [ 'mpsongs' ], 'readonly' ) var objectStore = transaction.objectStore( 'mpsongs' ) var req = objectStore.get( filePath ) req.onerror = function ( evt ) { console.log( 'error' ) console.log( evt ) } req.onsuccess = function ( evt ) { if ( evt.target.result && typeof evt.target.result.mtime !== 'undefined' && stats.mtime <= evt.target.result.mtime ) { return } QUEUE_SCAN.add( function ( done ) { //console.log( filePath ) FFMPEG.ffprobe( filePath, function ( err, data ) { if ( err ) { //console.log( mime ) //doError.call( that, err.message ) return done() } var tags = data.format.tags || {} var track = ( tags.track || tags.TRACK || '' ).toString().split( '/' )[ 0 ].trim() if ( track !== '' ) { track = parseInt( track ) } that.emit( 'scannedsong', { track: track, title: ( tags.title || tags.TITLE || fileName ).toString().trim(), artist: ( tags.artist || tags.ARTIST || 'unknown artist' ).toString().trim(), album: ( tags.album || tags.ALBUM || 'unknown album' ).toString().trim(), duration: parseFloat( data.format.duration || 0 ), path: filePath, mtime: stats.mtime } ) done() } ) } ) } } /* else if ( VIDEO_REGEX.test( mime ) ) { } */ // attempt to handle octet stream, maybe? } // fires when the server is connected to indexedDB function Server_init() { doMessage.call( this, 'mpserver running' ) //this.scanLibrary() } function Server_log( type, str ) { this.emit( 'log', { type: type, message: str } ) } var server = new Server() module.exports = function ( win ) { WIN = win || {} WIN.mpserver = server AUDIO = WIN.document.getElementById( 'mp-audio' ) return WIN.mpserver } }
const path = require('path'); const ffmpeg = require('fluent-ffmpeg'); const command = ffmpeg(); ffmpeg.setFfmpegPath('./ffmpeg/ffmpeg.exe'); ffmpeg.setFfprobePath('./ffmpeg/ffprobe.exe'); ffmpeg.ffprobe('./file.flac', function(err, res) { console.log(JSON.stringify(res, null, ' ')); }); ffmpeg.ffprobe('./no-info.flac', function(err, res) { console.log(JSON.stringify(res, null, ' ')); });
'use strict'; var _ = require('lodash'); var Video = require('./video.model'); var fs = require('fs'); var path = require('path'); var crypto = require('crypto'); var config = require('../../config/environment'); var ffmpeg = require('fluent-ffmpeg'); // Set FFMPEG path ffmpeg.setFfmpegPath('/home/gmonne/bin/ffmpeg'); ffmpeg.setFfprobePath('/home/gmonne/bin/ffprobe'); // Get list of videos exports.index = function(req, res) { var query, params = {}; if (req.query._category){ params._category = req.query._category; } if (req.query.enabled) { params.enabled = req.query.enabled; } query = Video .find(params) .populate('_category', 'name') .populate('createdBy', 'name') .populate('updatedBy', 'name'); if (req.query.limitTo){ query = query.limit(parseInt(req.query.limitTo)); } query.exec(function (err, videos) { if(err) { return handleError(res, err); } return res.json(200, videos); });