mirror of
https://github.com/danbulant/node-html-parser
synced 2026-06-14 04:01:11 +00:00
added html validation
This commit is contained in:
parent
ba58c88fcd
commit
ddf2b8e07e
13 changed files with 3236 additions and 165 deletions
18
.vscode/launch.json
vendored
Normal file
18
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"program": "${workspaceFolder}/test.js",
|
||||
"preLaunchTask": "tsc: build - tsconfig.json",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
18
gulpfile.js
18
gulpfile.js
|
|
@ -1,18 +0,0 @@
|
|||
const gulp = require('gulp');
|
||||
const shell = require('gulp-shell');
|
||||
const sequence = require('gulp-sequence');
|
||||
|
||||
gulp.task('clean', () => {
|
||||
const del = require('del');
|
||||
return del('./dist/');
|
||||
});
|
||||
|
||||
gulp.task('compile-ts', shell.task('tsc -m commonjs'));
|
||||
|
||||
gulp.task('compile-ts-umd', shell.task('tsc -t es5 -m umd --outDir ./dist/umd/'));
|
||||
|
||||
gulp.task('watch-ts', shell.task('tsc -w -t es5 -m umd --outDir ./dist/umd/'));
|
||||
|
||||
gulp.task('default', sequence('clean', 'compile-ts', 'compile-ts-umd'));
|
||||
|
||||
gulp.task('dev', ['watch-ts']);
|
||||
15
package.json
15
package.json
|
|
@ -6,9 +6,11 @@
|
|||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"test": "mocha",
|
||||
"build": "gulp",
|
||||
"pretest": "gulp",
|
||||
"prepare": "gulp"
|
||||
"clean": "del-cli ./dist/",
|
||||
"ts:cjs": "tsc -m commonjs",
|
||||
"ts:umd": "tsc -t es5 -m umd --outDir ./dist/umd/",
|
||||
"build": "npm run clean && npm run ts:cjs && npm run ts:umd",
|
||||
"dev": "tsc -w"
|
||||
},
|
||||
"keywords": [
|
||||
"fast html parser nodejs typescript"
|
||||
|
|
@ -26,10 +28,7 @@
|
|||
"@types/he": "latest",
|
||||
"@types/node": "latest",
|
||||
"blanket": "latest",
|
||||
"del": "latest",
|
||||
"gulp": "latest",
|
||||
"gulp-sequence": "latest",
|
||||
"gulp-shell": "latest",
|
||||
"del-cli": "latest",
|
||||
"mocha": "latest",
|
||||
"should": "latest",
|
||||
"spec": "latest",
|
||||
|
|
@ -58,4 +57,4 @@
|
|||
"url": "https://github.com/taoqf/node-fast-html-parser/issues"
|
||||
},
|
||||
"homepage": "https://github.com/taoqf/node-fast-html-parser"
|
||||
}
|
||||
}
|
||||
322
src/index.ts
322
src/index.ts
|
|
@ -94,28 +94,23 @@ function arr_back<T>(arr: T[]) {
|
|||
export class HTMLElement extends Node {
|
||||
private _attrs: Attributes;
|
||||
private _rawAttrs: RawAttributes;
|
||||
id: string;
|
||||
classNames = [] as string[];
|
||||
tagName: string;
|
||||
rawAttrs: string;
|
||||
public id: string;
|
||||
public classNames = [] as string[];
|
||||
/**
|
||||
* Node Type declaration.
|
||||
* @type {Number}
|
||||
*/
|
||||
nodeType = NodeType.ELEMENT_NODE;
|
||||
public nodeType = NodeType.ELEMENT_NODE;
|
||||
/**
|
||||
* Creates an instance of HTMLElement.
|
||||
* @param {string} name tagName
|
||||
* @param {KeyAttributes} keyAttrs id and class attribute
|
||||
* @param {string} [rawAttrs] attributes in string
|
||||
* @param keyAttrs id and class attribute
|
||||
* @param [rawAttrs] attributes in string
|
||||
*
|
||||
* @memberof HTMLElement
|
||||
*/
|
||||
constructor(name: string, keyAttrs: KeyAttributes, rawAttrs?: string) {
|
||||
constructor(public tagName: string, keyAttrs: KeyAttributes, private rawAttrs = '', public parentNode = null as Node) {
|
||||
super();
|
||||
this.tagName = name;
|
||||
this.rawAttrs = rawAttrs || '';
|
||||
// this.parentNode = null;
|
||||
this.parentNode = parentNode || null;
|
||||
this.childNodes = [];
|
||||
if (keyAttrs.id) {
|
||||
this.id = keyAttrs.id;
|
||||
|
|
@ -124,6 +119,30 @@ export class HTMLElement extends Node {
|
|||
this.classNames = keyAttrs.class.split(/\s+/);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Remove Child element from childNodes array
|
||||
* @param {HTMLElement} node node to remove
|
||||
*/
|
||||
public removeChild(node: Node) {
|
||||
this.childNodes = this.childNodes.filter((child) => {
|
||||
return (child !== node);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Exchanges given child with new child
|
||||
* @param {HTMLElement} oldNode node to exchange
|
||||
* @param {HTMLElement} newNode new node
|
||||
*/
|
||||
public exchangeChild(oldNode: Node, newNode: Node) {
|
||||
let idx = -1;
|
||||
for (let i = 0; i < this.childNodes.length; i++) {
|
||||
if (this.childNodes[i] === oldNode) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.childNodes[idx] = newNode;
|
||||
}
|
||||
/**
|
||||
* Get escpaed (as-it) text value of current node and its children.
|
||||
* @return {string} text content
|
||||
|
|
@ -184,7 +203,7 @@ export class HTMLElement extends Node {
|
|||
.join('\n').replace(/\s+$/, ''); // trimRight;
|
||||
}
|
||||
|
||||
toString() {
|
||||
public toString() {
|
||||
const tag = this.tagName;
|
||||
if (tag) {
|
||||
const is_un_closed = /^meta$/i.test(tag);
|
||||
|
|
@ -208,7 +227,7 @@ export class HTMLElement extends Node {
|
|||
}).join('');
|
||||
}
|
||||
|
||||
set_content(content: string | Node | Node[]) {
|
||||
public set_content(content: string | Node | Node[]) {
|
||||
if (content instanceof Node) {
|
||||
content = [content];
|
||||
} else if (typeof content == 'string') {
|
||||
|
|
@ -227,23 +246,20 @@ export class HTMLElement extends Node {
|
|||
* @param {RegExp} pattern pattern to find
|
||||
* @return {HTMLElement} reference to current node
|
||||
*/
|
||||
trimRight(pattern: RegExp) {
|
||||
function dfs(node: Node) {
|
||||
for (let i = 0; i < node.childNodes.length; i++) {
|
||||
const childNode = node.childNodes[i];
|
||||
if (childNode.nodeType === NodeType.ELEMENT_NODE) {
|
||||
dfs(childNode);
|
||||
} else {
|
||||
const index = childNode.rawText.search(pattern);
|
||||
if (index > -1) {
|
||||
childNode.rawText = childNode.rawText.substr(0, index);
|
||||
// trim all following nodes.
|
||||
node.childNodes.length = i + 1;
|
||||
}
|
||||
public trimRight(pattern: RegExp) {
|
||||
for (let i = 0; i < this.childNodes.length; i++) {
|
||||
const childNode = this.childNodes[i];
|
||||
if (childNode.nodeType === NodeType.ELEMENT_NODE) {
|
||||
(childNode as HTMLElement).trimRight(pattern);
|
||||
} else {
|
||||
const index = childNode.rawText.search(pattern);
|
||||
if (index > -1) {
|
||||
childNode.rawText = childNode.rawText.substr(0, index);
|
||||
// trim all following nodes.
|
||||
this.childNodes.length = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
dfs(this);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
|
|
@ -280,7 +296,7 @@ export class HTMLElement extends Node {
|
|||
* Remove whitespaces in this sub tree.
|
||||
* @return {HTMLElement} pointer to this
|
||||
*/
|
||||
removeWhitespace() {
|
||||
public removeWhitespace() {
|
||||
let o = 0;
|
||||
for (let i = 0; i < this.childNodes.length; i++) {
|
||||
const node = this.childNodes[i];
|
||||
|
|
@ -303,7 +319,7 @@ export class HTMLElement extends Node {
|
|||
* @param {Matcher} selector A Matcher instance
|
||||
* @return {HTMLElement[]} matching elements
|
||||
*/
|
||||
querySelectorAll(selector: string | Matcher) {
|
||||
public querySelectorAll(selector: string | Matcher) {
|
||||
let matcher: Matcher;
|
||||
if (selector instanceof Matcher) {
|
||||
matcher = selector;
|
||||
|
|
@ -311,7 +327,7 @@ export class HTMLElement extends Node {
|
|||
} else {
|
||||
matcher = new Matcher(selector);
|
||||
}
|
||||
const res = [] as Node[];
|
||||
const res = [] as HTMLElement[];
|
||||
const stack = [] as { 0: Node; 1: 0 | 1; 2: boolean; }[];
|
||||
for (let i = 0; i < this.childNodes.length; i++) {
|
||||
stack.push([this.childNodes[i], 0, false]);
|
||||
|
|
@ -326,7 +342,7 @@ export class HTMLElement extends Node {
|
|||
}
|
||||
if (state[2] = matcher.advance(el)) {
|
||||
if (matcher.matched) {
|
||||
res.push(el);
|
||||
res.push(el as HTMLElement);
|
||||
// no need to go further.
|
||||
matcher.rewind();
|
||||
stack.pop();
|
||||
|
|
@ -352,7 +368,7 @@ export class HTMLElement extends Node {
|
|||
* @param {Matcher} selector A Matcher instance
|
||||
* @return {HTMLElement} matching node
|
||||
*/
|
||||
querySelector(selector: string | Matcher) {
|
||||
public querySelector(selector: string | Matcher) {
|
||||
let matcher: Matcher;
|
||||
if (selector instanceof Matcher) {
|
||||
matcher = selector;
|
||||
|
|
@ -374,7 +390,7 @@ export class HTMLElement extends Node {
|
|||
}
|
||||
if (state[2] = matcher.advance(el)) {
|
||||
if (matcher.matched) {
|
||||
return el;
|
||||
return el as HTMLElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -395,9 +411,12 @@ export class HTMLElement extends Node {
|
|||
* @param {Node} node node to append
|
||||
* @return {Node} node appended
|
||||
*/
|
||||
appendChild(node: Node) {
|
||||
public appendChild<T extends Node = Node>(node: T) {
|
||||
// node.parentNode = this;
|
||||
this.childNodes.push(node);
|
||||
if (node instanceof HTMLElement) {
|
||||
node.parentNode = this;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
|
|
@ -463,104 +482,104 @@ let pMatchFunctionCache = {} as { [name: string]: MatherFunction };
|
|||
/**
|
||||
* Function cache
|
||||
*/
|
||||
let functionCache = {
|
||||
"f145":function(el: any,tagName: string,classes: any[],attr_key: string,value: string){
|
||||
const functionCache = {
|
||||
"f145": function (el: HTMLElement, tagName: string, classes: string[], attr_key: string, value: string) {
|
||||
"use strict";
|
||||
tagName = tagName||"";
|
||||
classes = classes||[];
|
||||
attr_key = attr_key||"";
|
||||
value = value||"";
|
||||
tagName = tagName || "";
|
||||
classes = classes || [];
|
||||
attr_key = attr_key || "";
|
||||
value = value || "";
|
||||
if (el.id != tagName.substr(1)) return false;
|
||||
for (var cls = classes, i = 0; i < cls.length; i++) if (el.classNames.indexOf(cls[i]) === -1) return false;
|
||||
return true;
|
||||
},
|
||||
"f45":function(el: any,tagName: string,classes: any[],attr_key: string,value: string){
|
||||
"use strict";
|
||||
tagName = tagName||"";
|
||||
classes = classes||[];
|
||||
attr_key = attr_key||"";
|
||||
value = value||"";
|
||||
for (var cls = classes, i = 0; i < cls.length; i++) if (el.classNames.indexOf(cls[i]) === -1) return false;
|
||||
for (let cls = classes, i = 0; i < cls.length; i++) if (el.classNames.indexOf(cls[i]) === -1) return false;
|
||||
return true;
|
||||
},
|
||||
"f15":function(el: any,tagName: string,classes: any[],attr_key: string,value: string){
|
||||
"f45": function (el: HTMLElement, tagName: string, classes: string[], attr_key: string, value: string) {
|
||||
"use strict";
|
||||
tagName = tagName||"";
|
||||
classes = classes||[];
|
||||
attr_key = attr_key||"";
|
||||
value = value||"";
|
||||
tagName = tagName || "";
|
||||
classes = classes || [];
|
||||
attr_key = attr_key || "";
|
||||
value = value || "";
|
||||
for (let cls = classes, i = 0; i < cls.length; i++) if (el.classNames.indexOf(cls[i]) === -1) return false;
|
||||
return true;
|
||||
},
|
||||
"f15": function (el: HTMLElement, tagName: string, classes: string[], attr_key: string, value: string) {
|
||||
"use strict";
|
||||
tagName = tagName || "";
|
||||
classes = classes || [];
|
||||
attr_key = attr_key || "";
|
||||
value = value || "";
|
||||
if (el.id != tagName.substr(1)) return false;
|
||||
return true;
|
||||
},
|
||||
"f1":function(el: any,tagName: string,classes: any[],attr_key: string,value: string){
|
||||
"f1": function (el: HTMLElement, tagName: string, classes: string[], attr_key: string, value: string) {
|
||||
"use strict";
|
||||
tagName = tagName||"";
|
||||
classes = classes||[];
|
||||
attr_key = attr_key||"";
|
||||
value = value||"";
|
||||
tagName = tagName || "";
|
||||
classes = classes || [];
|
||||
attr_key = attr_key || "";
|
||||
value = value || "";
|
||||
if (el.id != tagName.substr(1)) return false;
|
||||
},
|
||||
"f5":function(el: any,tagName: string,classes: any[],attr_key: string,value: string){
|
||||
"f5": function (el: HTMLElement, tagName: string, classes: string[], attr_key: string, value: string) {
|
||||
"use strict";
|
||||
el = el||{};
|
||||
tagName = tagName||"";
|
||||
classes = classes||[];
|
||||
attr_key = attr_key||"";
|
||||
value = value||"";
|
||||
el = el || {} as HTMLElement;
|
||||
tagName = tagName || "";
|
||||
classes = classes || [];
|
||||
attr_key = attr_key || "";
|
||||
value = value || "";
|
||||
return true;
|
||||
},
|
||||
"f245":function(el: any,tagName: string,classes: any[],attr_key: string,value: string){
|
||||
"f245": function (el: HTMLElement, tagName: string, classes: string[], attr_key: string, value: string) {
|
||||
"use strict";
|
||||
tagName = tagName||"";
|
||||
classes = classes||[];
|
||||
attr_key = attr_key||"";
|
||||
value = value||"";
|
||||
var attrs = el.attributes;for (var key in attrs){const val = attrs[key]; if (key == attr_key && val == value){return true;}} return false;
|
||||
// for (var cls = classes, i = 0; i < cls.length; i++) {if (el.classNames.indexOf(cls[i]) === -1){ return false;}}
|
||||
tagName = tagName || "";
|
||||
classes = classes || [];
|
||||
attr_key = attr_key || "";
|
||||
value = value || "";
|
||||
let attrs = el.attributes; for (let key in attrs) { const val = attrs[key]; if (key == attr_key && val == value) { return true; } } return false;
|
||||
// for (let cls = classes, i = 0; i < cls.length; i++) {if (el.classNames.indexOf(cls[i]) === -1){ return false;}}
|
||||
// return true;
|
||||
},
|
||||
"f25":function(el: any,tagName: string,classes: any[],attr_key: string,value: string){
|
||||
"f25": function (el: HTMLElement, tagName: string, classes: string[], attr_key: string, value: string) {
|
||||
"use strict";
|
||||
tagName = tagName||"";
|
||||
classes = classes||[];
|
||||
attr_key = attr_key||"";
|
||||
value = value||"";
|
||||
var attrs = el.attributes;for (var key in attrs){const val = attrs[key]; if (key == attr_key && val == value){return true;}} return false;
|
||||
tagName = tagName || "";
|
||||
classes = classes || [];
|
||||
attr_key = attr_key || "";
|
||||
value = value || "";
|
||||
let attrs = el.attributes; for (let key in attrs) { const val = attrs[key]; if (key == attr_key && val == value) { return true; } } return false;
|
||||
//return true;
|
||||
},
|
||||
"f2":function(el: any,tagName: string,classes: any[],attr_key: string,value: string){
|
||||
"f2": function (el: HTMLElement, tagName: string, classes: string[], attr_key: string, value: string) {
|
||||
"use strict";
|
||||
tagName = tagName||"";
|
||||
classes = classes||[];
|
||||
attr_key = attr_key||"";
|
||||
value = value||"";
|
||||
var attrs = el.attributes;for (var key in attrs){const val = attrs[key]; if (key == attr_key && val == value){return true;}} return false;
|
||||
tagName = tagName || "";
|
||||
classes = classes || [];
|
||||
attr_key = attr_key || "";
|
||||
value = value || "";
|
||||
let attrs = el.attributes; for (let key in attrs) { const val = attrs[key]; if (key == attr_key && val == value) { return true; } } return false;
|
||||
},
|
||||
"f345":function(el: any,tagName: string,classes: any[],attr_key: string,value: string){
|
||||
"f345": function (el: HTMLElement, tagName: string, classes: string[], attr_key: string, value: string) {
|
||||
"use strict";
|
||||
tagName = tagName||"";
|
||||
classes = classes||[];
|
||||
attr_key = attr_key||"";
|
||||
value = value||"";
|
||||
tagName = tagName || "";
|
||||
classes = classes || [];
|
||||
attr_key = attr_key || "";
|
||||
value = value || "";
|
||||
if (el.tagName != tagName) return false;
|
||||
for (var cls = classes, i = 0; i < cls.length; i++) if (el.classNames.indexOf(cls[i]) === -1) return false;
|
||||
for (let cls = classes, i = 0; i < cls.length; i++) if (el.classNames.indexOf(cls[i]) === -1) return false;
|
||||
return true;
|
||||
},
|
||||
"f35":function(el: any,tagName: string,classes: any[],attr_key: string,value: string){
|
||||
"f35": function (el: HTMLElement, tagName: string, classes: string[], attr_key: string, value: string) {
|
||||
"use strict";
|
||||
tagName = tagName||"";
|
||||
classes = classes||[];
|
||||
attr_key = attr_key||"";
|
||||
value = value||"";
|
||||
tagName = tagName || "";
|
||||
classes = classes || [];
|
||||
attr_key = attr_key || "";
|
||||
value = value || "";
|
||||
if (el.tagName != tagName) return false;
|
||||
return true;
|
||||
},
|
||||
"f3":function(el: any,tagName: string,classes: any[],attr_key: string,value: string){
|
||||
"f3": function (el: HTMLElement, tagName: string, classes: string[], attr_key: string, value: string) {
|
||||
"use strict";
|
||||
tagName = tagName||"";
|
||||
classes = classes||[];
|
||||
attr_key = attr_key||"";
|
||||
value = value||"";
|
||||
tagName = tagName || "";
|
||||
classes = classes || [];
|
||||
attr_key = attr_key || "";
|
||||
value = value || "";
|
||||
if (el.tagName != tagName) return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -606,7 +625,7 @@ export class Matcher {
|
|||
}
|
||||
value = matcher[7] || matcher[8];
|
||||
|
||||
source += `var attrs = el.attributes;for (var key in attrs){const val = attrs[key]; if (key == "${attr_key}" && val == "${value}"){return true;}} return false;`;//2
|
||||
source += `let attrs = el.attributes;for (let key in attrs){const val = attrs[key]; if (key == "${attr_key}" && val == "${value}"){return true;}} return false;`;//2
|
||||
function_name += '2';
|
||||
} else {
|
||||
source += 'if (el.tagName != ' + JSON.stringify(tagName) + ') return false;';//3
|
||||
|
|
@ -614,19 +633,19 @@ export class Matcher {
|
|||
}
|
||||
}
|
||||
if (classes.length > 0) {
|
||||
source += 'for (var cls = ' + JSON.stringify(classes) + ', i = 0; i < cls.length; i++) if (el.classNames.indexOf(cls[i]) === -1) return false;';//4
|
||||
source += 'for (let cls = ' + JSON.stringify(classes) + ', i = 0; i < cls.length; i++) if (el.classNames.indexOf(cls[i]) === -1) return false;';//4
|
||||
function_name += '4';
|
||||
}
|
||||
source += 'return true;';//5
|
||||
function_name += '5';
|
||||
let obj = {
|
||||
func : functionCache[function_name],
|
||||
tagName: tagName||"",
|
||||
classes : classes||"",
|
||||
attr_key : attr_key||"",
|
||||
value : value||""
|
||||
func: functionCache[function_name],
|
||||
tagName: tagName || "",
|
||||
classes: classes || "",
|
||||
attr_key: attr_key || "",
|
||||
value: value || ""
|
||||
}
|
||||
source = source||"";
|
||||
source = source || "";
|
||||
return pMatchFunctionCache[matcher] = obj as MatherFunction;
|
||||
});
|
||||
}
|
||||
|
|
@ -637,7 +656,7 @@ export class Matcher {
|
|||
*/
|
||||
advance(el: Node) {
|
||||
if (this.nextMatch < this.matchers.length &&
|
||||
this.matchers[this.nextMatch].func(el,this.matchers[this.nextMatch].tagName,this.matchers[this.nextMatch].classes,this.matchers[this.nextMatch].attr_key,this.matchers[this.nextMatch].value)) {
|
||||
this.matchers[this.nextMatch].func(el, this.matchers[this.nextMatch].tagName, this.matchers[this.nextMatch].classes, this.matchers[this.nextMatch].attr_key, this.matchers[this.nextMatch].value)) {
|
||||
this.nextMatch++;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -675,19 +694,28 @@ export class Matcher {
|
|||
const kMarkupPattern = /<!--[^]*?(?=-->)-->|<(\/?)([a-z][-.0-9_a-z]*)\s*([^>]*?)(\/?)>/ig;
|
||||
const kAttributePattern = /(^|\s)(id|class)\s*=\s*("([^"]+)"|'([^']+)'|(\S+))/ig;
|
||||
const kSelfClosingElements = {
|
||||
meta: true,
|
||||
img: true,
|
||||
link: true,
|
||||
input: true,
|
||||
area: true,
|
||||
base: true,
|
||||
br: true,
|
||||
hr: true
|
||||
col: true,
|
||||
hr: true,
|
||||
img: true,
|
||||
input: true,
|
||||
link: true,
|
||||
meta: true
|
||||
};
|
||||
const kElementsClosedByOpening = {
|
||||
li: { li: true },
|
||||
p: { p: true, div: true },
|
||||
b: { div: true },
|
||||
td: { td: true, th: true },
|
||||
th: { td: true, th: true }
|
||||
th: { td: true, th: true },
|
||||
h1: { h1: true },
|
||||
h2: { h2: true },
|
||||
h3: { h3: true },
|
||||
h4: { h4: true },
|
||||
h5: { h5: true },
|
||||
h6: { h6: true }
|
||||
};
|
||||
const kElementsClosedByClosing = {
|
||||
li: { ul: true, ol: true },
|
||||
|
|
@ -712,13 +740,13 @@ const kBlockTextElements = {
|
|||
* @return {HTMLElement} root element
|
||||
*/
|
||||
export function parse(data: string, options?: {
|
||||
lowerCaseTagName: boolean;
|
||||
lowerCaseTagName?: boolean;
|
||||
noFix?: boolean;
|
||||
}) {
|
||||
const root = new HTMLElement(null, {});
|
||||
let currentParent = root;
|
||||
const stack = [root];
|
||||
let lastTextPos = -1;
|
||||
|
||||
options = options || {} as any;
|
||||
let match: RegExpExecArray;
|
||||
while (match = kMarkupPattern.exec(data)) {
|
||||
|
|
@ -738,10 +766,11 @@ export function parse(data: string, options?: {
|
|||
match[2] = match[2].toLowerCase();
|
||||
if (!match[1]) {
|
||||
// not </ tags
|
||||
var attrs = {};
|
||||
for (var attMatch; attMatch = kAttributePattern.exec(match[3]);)
|
||||
let attrs = {};
|
||||
for (let attMatch; attMatch = kAttributePattern.exec(match[3]);) {
|
||||
attrs[attMatch[2]] = attMatch[4] || attMatch[5] || attMatch[6];
|
||||
// console.log(attrs);
|
||||
}
|
||||
|
||||
if (!match[4] && kElementsClosedByOpening[currentParent.tagName]) {
|
||||
if (kElementsClosedByOpening[currentParent.tagName][match[2]]) {
|
||||
stack.pop();
|
||||
|
|
@ -749,12 +778,12 @@ export function parse(data: string, options?: {
|
|||
}
|
||||
}
|
||||
currentParent = currentParent.appendChild(
|
||||
new HTMLElement(match[2], attrs, match[3])) as HTMLElement;
|
||||
new HTMLElement(match[2], attrs, match[3]));
|
||||
stack.push(currentParent);
|
||||
if (kBlockTextElements[match[2]]) {
|
||||
// a little test to find next </script> or </style> ...
|
||||
var closeMarkup = '</' + match[2] + '>';
|
||||
var index = data.indexOf(closeMarkup, kMarkupPattern.lastIndex);
|
||||
let closeMarkup = '</' + match[2] + '>';
|
||||
let index = data.indexOf(closeMarkup, kMarkupPattern.lastIndex);
|
||||
if (options[match[2]]) {
|
||||
let text: string;
|
||||
if (index == -1) {
|
||||
|
|
@ -763,8 +792,9 @@ export function parse(data: string, options?: {
|
|||
} else {
|
||||
text = data.substring(kMarkupPattern.lastIndex, index);
|
||||
}
|
||||
if (text.length > 0)
|
||||
if (text.length > 0) {
|
||||
currentParent.appendChild(new TextNode(text));
|
||||
}
|
||||
}
|
||||
if (index == -1) {
|
||||
lastTextPos = kMarkupPattern.lastIndex = data.length + 1;
|
||||
|
|
@ -797,5 +827,43 @@ export function parse(data: string, options?: {
|
|||
}
|
||||
}
|
||||
}
|
||||
return root;
|
||||
type Response = (HTMLElement | TextNode) & { valid: boolean; };
|
||||
const valid = !!(stack.length === 1);
|
||||
if (!options.noFix) {
|
||||
const response = root as Response;
|
||||
response.valid = valid;
|
||||
while (stack.length > 1) {
|
||||
// Handle each error elements.
|
||||
const last = stack.pop();
|
||||
const oneBefore = arr_back(stack);
|
||||
if (last.parentNode && (last.parentNode as HTMLElement).parentNode) {
|
||||
if (last.parentNode === oneBefore && last.tagName === oneBefore.tagName) {
|
||||
// Pair error case <h3> <h3> handle : Fixes to <h3> </h3>
|
||||
oneBefore.removeChild(last);
|
||||
last.childNodes.forEach((child) => {
|
||||
(oneBefore.parentNode as HTMLElement).appendChild(child);
|
||||
});
|
||||
stack.pop();
|
||||
} else {
|
||||
// Single error <div> <h3> </div> handle: Just removes <h3>
|
||||
oneBefore.removeChild(last);
|
||||
last.childNodes.forEach((child) => {
|
||||
oneBefore.appendChild(child);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// If it's final element just skip.
|
||||
}
|
||||
}
|
||||
response.childNodes.forEach((node) => {
|
||||
if (node instanceof HTMLElement) {
|
||||
node.parentNode = null;
|
||||
}
|
||||
});
|
||||
return response;
|
||||
} else {
|
||||
const response = new TextNode(data) as Response;
|
||||
response.valid = valid;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
9
test.js
Normal file
9
test.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
var HTMLParser = require('./dist');
|
||||
var parseHTML = HTMLParser.parse;
|
||||
var result = parseHTML('<div data-id=1><h3 data-id=2><h3><div>', {
|
||||
fixIssues: true,
|
||||
validate: true
|
||||
});
|
||||
|
||||
console.log('fffffff', result.toString())
|
||||
97
test/html.js
97
test/html.js
|
|
@ -17,11 +17,6 @@ describe('HTML Parser', function () {
|
|||
var withIdEl = new HTMLElement('p', { id: 'id' });
|
||||
var withClassNameEl = new HTMLElement('a', { class: 'a b' });
|
||||
|
||||
// console.log(util.inspect([withIdEl, withClassNameEl], {
|
||||
// showHidden: true,
|
||||
// depth: null
|
||||
// }));
|
||||
|
||||
matcher.advance(MatchesNothingButStarEl).should.not.be.ok; // #id
|
||||
matcher.advance(withClassNameEl).should.not.be.ok; // #id
|
||||
matcher.advance(withIdEl).should.be.ok; // #id
|
||||
|
|
@ -151,6 +146,84 @@ describe('HTML Parser', function () {
|
|||
|
||||
});
|
||||
|
||||
// Test for broken tags. <h3>something<h3>
|
||||
|
||||
it('should parse "<div><h3>content<h3> <span> other <span></div>" (fix h3, span closing tag) very fast', function () {
|
||||
var root = parseHTML(fs.readFileSync(__dirname + '/html/incomplete-script').toString());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('parseWithValidation', function () {
|
||||
// parse with validation tests
|
||||
|
||||
it('should return Object with valid: true. does not count <p><p></p> as error. instead fixes it to <p></p><p></p>', function () {
|
||||
var result = parseHTML('<p><p></p>');
|
||||
result.valid.should.eql(true);
|
||||
})
|
||||
|
||||
it('should return Object with valid: true. does not count <p><p/></p> as error. instead fixes it to <p><p></p></p>', function () {
|
||||
var result = parseHTML('<p><p/></p>');
|
||||
result.valid.should.eql(true);
|
||||
})
|
||||
|
||||
it('should return Object with valid: false. does not count <p><h3></p> as error', function () {
|
||||
var result = parseHTML('<p><h3></p>');
|
||||
result.valid.should.eql(false);
|
||||
})
|
||||
|
||||
it('hillcrestpartyrentals.html should return Object with valid: false. not closing <p> tag on line 476', function () {
|
||||
var result = parseHTML(fs.readFileSync(__dirname + '/html/hillcrestpartyrentals.html').toString(), {
|
||||
noFix: true
|
||||
});
|
||||
result.valid.should.eql(false);
|
||||
})
|
||||
|
||||
it('google.html should return Object with valid: true', function () {
|
||||
var result = parseHTML(fs.readFileSync(__dirname + '/html/google.html').toString(), {
|
||||
noFix: true
|
||||
});
|
||||
result.valid.should.eql(true);
|
||||
})
|
||||
|
||||
it('gmail.html should return Object with valid: true', function () {
|
||||
var result = parseHTML(fs.readFileSync(__dirname + '/html/gmail.html').toString(), {
|
||||
noFix: true
|
||||
});
|
||||
result.valid.should.eql(true);
|
||||
})
|
||||
|
||||
it('ffmpeg.html should return Object with valid: false (extra opening <div>', function () {
|
||||
var result = parseHTML(fs.readFileSync(__dirname + '/html/ffmpeg.html').toString(), {
|
||||
noFix: true
|
||||
});
|
||||
result.valid.should.eql(false);
|
||||
})
|
||||
|
||||
// fix issue speed test
|
||||
|
||||
it('should fix "<div><h3><h3><div>" to "<div><h3></h3></div>"', function () {
|
||||
var result = parseHTML('<div data-id=1><h3 data-id=2><h3><div>');
|
||||
result.valid.should.eql(false);
|
||||
result.toString().should.eql('<div data-id=1><h3 data-id=2></h3></div>');
|
||||
})
|
||||
|
||||
it('should fix "<div><h3><h3><span><span><div>" to "<div><h3></h3><span></span></div>"', function () {
|
||||
var result = parseHTML('<div><h3><h3><span><span><div>');
|
||||
result.valid.should.eql(false);
|
||||
result.toString().should.eql('<div><h3></h3><span></span></div>');
|
||||
})
|
||||
|
||||
it('gmail.html should return Object with valid: true', function () {
|
||||
var result = parseHTML(fs.readFileSync(__dirname + '/html/gmail.html').toString().replace(/<\//gi, '<'));
|
||||
result.valid.should.eql(false);
|
||||
})
|
||||
|
||||
it('gmail.html should return Object with valid: true', function () {
|
||||
var result = parseHTML(fs.readFileSync(__dirname + '/html/nice.html').toString().replace(/<\//gi, '<'));
|
||||
result.valid.should.eql(false);
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
describe('TextNode', function () {
|
||||
|
|
@ -257,7 +330,7 @@ describe('HTML Parser', function () {
|
|||
});
|
||||
|
||||
describe('stringify', function () {
|
||||
describe('#toString()', function () {
|
||||
it('#toString()', function () {
|
||||
const html = '<p id="id" data-feidao-actions="ssss"><a class=\'cls\'>Hello</a><ul><li>aaaaa</li></ul><span>bbb</span></p>';
|
||||
const root = parseHTML(html);
|
||||
root.toString().should.eql(html)
|
||||
|
|
@ -273,12 +346,12 @@ describe('HTML Parser', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Custom Element multiple dash', function () {
|
||||
it('parse "<my-new-widget></my-new-widget>" tagName should be "my-new-widget"', function () {
|
||||
describe('Custom Element multiple dash', function () {
|
||||
it('parse "<my-new-widget></my-new-widget>" tagName should be "my-new-widget"', function () {
|
||||
|
||||
var root = parseHTML('<my-new-widget></my-new-widget>');
|
||||
var root = parseHTML('<my-new-widget></my-new-widget>');
|
||||
|
||||
root.firstChild.tagName.should.eql('my-new-widget');
|
||||
});
|
||||
});
|
||||
root.firstChild.tagName.should.eql('my-new-widget');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
719
test/html/ffmpeg.html
Normal file
719
test/html/ffmpeg.html
Normal file
|
|
@ -0,0 +1,719 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<title>
|
||||
StreamingGuide – FFmpeg
|
||||
</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--[if IE]><script type="text/javascript">
|
||||
if (/^#__msie303:/.test(window.location.hash))
|
||||
window.location.replace(window.location.hash.replace(/^#__msie303:/, '#'));
|
||||
</script><![endif]-->
|
||||
<link rel="search" href="/search">
|
||||
<link rel="help" href="/wiki/TracGuide">
|
||||
<link rel="alternate" href="/wiki/StreamingGuide?format=txt" type="text/x-trac-wiki" title="Plain Text">
|
||||
<link rel="start" href="/wiki">
|
||||
<link rel="stylesheet" href="/chrome/common/css/trac.css" type="text/css">
|
||||
<link rel="stylesheet" href="/chrome/common/css/wiki.css" type="text/css">
|
||||
<link rel="stylesheet" href="/chrome/vote/css/tracvote.css" type="text/css">
|
||||
<link rel="stylesheet" href="/chrome/tags/css/tractags.css" type="text/css">
|
||||
<link rel="shortcut icon" href="/chrome/common/trac.ico" type="image/x-icon">
|
||||
<link rel="icon" href="/chrome/common/trac.ico" type="image/x-icon">
|
||||
<link type="application/opensearchdescription+xml" rel="search" href="/search/opensearch" title="Search FFmpeg">
|
||||
<script type="text/javascript" charset="utf-8" src="/chrome/common/js/jquery.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="/chrome/common/js/babel.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="/chrome/common/js/messages/en_US.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="/chrome/common/js/trac.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="/chrome/common/js/search.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="/chrome/common/js/folding.js"></script>
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function ($) {
|
||||
$("#content").find("h1,h2,h3,h4,h5,h6").addAnchor(_("Link to this section"));
|
||||
$("#content").find(".wikianchor").each(function () {
|
||||
$(this).addAnchor(babel.format(_("Link to #%(id)s"), { id: $(this).attr('id') }));
|
||||
});
|
||||
$(".foldable").enableFolding(true, true);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="banner">
|
||||
<div id="header">
|
||||
<a id="logo" href="https://ffmpeg.org"><img src="/ffmpeg-logo.png" alt="FFmpeg"></a>
|
||||
</div>
|
||||
<form id="search" action="/search" method="get">
|
||||
<div>
|
||||
<label for="proj-search">Search:</label>
|
||||
<input type="text" id="proj-search" name="q" size="18" value="">
|
||||
<input type="submit" value="Search">
|
||||
</div>
|
||||
</form>
|
||||
<div id="metanav" class="nav">
|
||||
<ul>
|
||||
<li class="first"><a href="/login">Login</a></li>
|
||||
<li><a href="/prefs">Preferences</a></li>
|
||||
<li><a href="/wiki/TracGuide">Help/Guide</a></li>
|
||||
<li><a href="/about">About Trac</a></li>
|
||||
<li class="last"><a href="/register">Register</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mainnav" class="nav">
|
||||
<ul>
|
||||
<li class="first active"><a href="/wiki">Wiki</a></li>
|
||||
<li><a href="/timeline">Timeline</a></li>
|
||||
<li><a href="/report">View Tickets</a></li>
|
||||
<li><a href="/search">Search</a></li>
|
||||
<li class="last"><a href="/tags">Tags</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="main">
|
||||
<div id="pagepath" class="noprint">
|
||||
<h1 style="display: inline;"><a class="pathentry first" title="View WikiStart" href="/wiki">wiki:</a></h1>
|
||||
<h1 style="display: inline; color: #b00;"><a class="pathentry" href="/wiki/StreamingGuide" title="View StreamingGuide">StreamingGuide</a></h1>
|
||||
</div>
|
||||
<div id="ctxtnav" class="nav">
|
||||
<h2>Context Navigation</h2>
|
||||
<ul>
|
||||
<li class="first"><span id="vote" title="Vote count"><img src="/chrome/vote/aupgray.png" alt="Up-vote"><span id="votes">+0</span><img
|
||||
src="/chrome/vote/adowngray.png" alt="Down-vote"></span></li>
|
||||
<li><a href="/wiki/WikiStart">Start Page</a></li>
|
||||
<li><a href="/wiki/TitleIndex">Index</a></li>
|
||||
<li class="last"><a href="/wiki/StreamingGuide?action=history">History</a></li>
|
||||
</ul>
|
||||
<hr>
|
||||
</div>
|
||||
<div id="content" class="wiki">
|
||||
<div class="wikipage searchable">
|
||||
|
||||
<div id="wikipage" class="trac-content">
|
||||
<h1 id="Streaming">Streaming<a class="anchor" href="#Streaming" title="Link to this section"> ¶</a></h1>
|
||||
<p>
|
||||
</p>
|
||||
<div class="wiki-toc">
|
||||
<h4>Contents</h4>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#The-reflag">The -re flag</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#Setting">Setting</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#Latency">Latency</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#CPUusageFilesize">CPU usage / File size</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#StreamingasimpleRTPaudiostreamfromFFmpeg">Streaming a simple RTP audio stream from FFmpeg</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#Codecs">Codecs</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#HTTPLiveStreamingandStreamingwithmultiplebitrates">HTTP Live Streaming and Streaming with multiple
|
||||
bitrates</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#SavingafileandStreamingatthesametime">Saving a file and Streaming at the same time</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#Transcodingrepeating">Transcoding / repeating</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#Adjustingbitratebasedonlineconditions">Adjusting bitrate based on line conditions</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#TroubleshootingStreaming">Troubleshooting Streaming</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#Pointtopointstreaming">Point to point streaming</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#Externallinks">External links</a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<p>
|
||||
</p>
|
||||
<p>
|
||||
FFmpeg can basically stream through one of two ways: It either streams to a some "other server", which re-streams
|
||||
for it to multiple clients, or it can stream via UDP/TCP directly to some single destination receiver, or
|
||||
alternatively directly to a multicast destination. Theoretically you might be able to send to multiple receivers
|
||||
via <a class="missing wiki">Creating Multiple Outputs?</a> but there is no built-in full blown server.
|
||||
</p>
|
||||
<p>
|
||||
Servers which can receive from FFmpeg (to restream to multiple clients) include <a class="wiki" href="/wiki/Streaming%20media%20with%20ffserver">ffserver</a>
|
||||
(linux only, though with cygwin it might work on windows), or <a class="ext-link" href="http://en.wikipedia.org/wiki/Wowza_Media_Server"><span
|
||||
class="icon"></span>Wowza Media Server</a>, or <a class="ext-link" href="http://en.wikipedia.org/wiki/Adobe_Flash_Media_Server"><span
|
||||
class="icon"></span>Flash Media Server</a>, Red5, or <a class="ext-link" href="https://en.wikipedia.org/wiki/List_of_streaming_media_systems#Servers"><span
|
||||
class="icon"></span>various others</a>. Even <a class="ext-link" href="http://en.wikipedia.org/wiki/VLC_media_player"><span
|
||||
class="icon"></span>VLC</a> can pick up the stream from ffmpeg, then redistribute it, acting as a server.
|
||||
Since FFmpeg is at times more efficient than VLC at doing the raw encoding, this can be a useful option compared
|
||||
to doing both transcoding and streaming in VLC. <a class="ext-link" href="https://www.vultr.com/docs/setup-nginx-on-ubuntu-to-stream-live-hls-video"><span
|
||||
class="icon"></span>Nginx</a> also has an rtmp redistribution plugin, as does <a class="ext-link" href="http://h264.code-shop.com/trac/wiki"><span
|
||||
class="icon"></span>apache etc.</a> and there is probably more out there for apache, etc.. You can also live
|
||||
stream to online redistribution servers like own3d.tv or justin.tv (for instance streaming your desktop). Also
|
||||
any <a class="ext-link" href="http://www.flashrealtime.com/list-of-available-rtmp-servers/"><span class="icon"></span>rtmp
|
||||
server</a> will most likely work to receive streams from FFmpeg (these typically require you to setup a running
|
||||
instance on a server).
|
||||
</p>
|
||||
<h2 id="The-reflag">The -re flag<a class="anchor" href="#The-reflag" title="Link to this section"> ¶</a></h2>
|
||||
<p>
|
||||
The FFmpeg's "-re" flag means to "Read input at native frame rate. Mainly used to simulate a grab device." i.e.
|
||||
if you wanted to stream a video file, then you would want to use this, otherwise it might stream it too fast (it
|
||||
attempts to stream at line speed by default). My guess is you typically don't want to use this flag when
|
||||
streaming from a live device, ever.
|
||||
</p>
|
||||
<h2 id="Setting">Setting<a class="anchor" href="#Setting" title="Link to this section"> ¶</a></h2>
|
||||
<p>
|
||||
Here is what another person once did for broadcast:
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -f dshow -i video="Virtual-Camera" -preset ultrafast -vcodec libx264 -tune zerolatency -b 900k -f mpegts udp://10.1.0.102:1234
|
||||
</pre>
|
||||
<p>
|
||||
And here is what another person <a class="ext-link" href="http://web.archiveorange.com/archive/v/DUtyPSinPqSIxjhedGQd"><span
|
||||
class="icon"></span>did</a>:
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -f dshow -i video="screen-capture-recorder":audio="Stereo Mix (IDT High Definition" \
|
||||
-vcodec libx264 -preset ultrafast -tune zerolatency -r 10 -async 1 -acodec libmp3lame -ab 24k -ar 22050 -bsf:v h264_mp4toannexb \
|
||||
-maxrate 750k -bufsize 3000k -f mpegts udp://192.168.5.215:48550
|
||||
</pre>
|
||||
<p>
|
||||
NB that they also (for directshow devices) had to adjust the rtbufsize in that example.
|
||||
</p>
|
||||
<p>
|
||||
You can see a description of what some of these means, (for example bufsize, bitrate settings) in the <a class="wiki"
|
||||
href="/wiki/Encode/H.264">Encode/H.264</a>.
|
||||
</p>
|
||||
<p>
|
||||
Here's how one guy broadcast a live stream (in this instance a <a class="missing wiki">Capture/Desktop#Windows?</a>
|
||||
screen capture device):
|
||||
</p>
|
||||
<pre class="wiki">$ ffmpeg -y -loglevel warning -f dshow -i video="screen-capture-recorder" -vf crop=690:388:136:0 -r 30 -s 962x388 -threads 2 -vcodec libx264 -vpre baseline -vpre my_ffpreset -f flv rtmp:///live/myStream.sdp
|
||||
</pre>
|
||||
<p>
|
||||
with a custom FFmpeg preset (libx264-my_ffpreset.ffpreset) in this case:
|
||||
</p>
|
||||
<pre class="wiki">coder=1
|
||||
flags2=+wpred+dct8x8
|
||||
level=31
|
||||
maxrate=1200000
|
||||
bufsize=200000
|
||||
wpredp=0
|
||||
g=60
|
||||
refs=1
|
||||
subq=3
|
||||
trellis=0
|
||||
bf=0
|
||||
rc_lookahead=0
|
||||
</pre>
|
||||
<p>
|
||||
Here is how you stream to twitch.tv or similar services (rtmp protocol), using ffmpeg 1.0 or ffmpeg-git (tested
|
||||
on 2012-11-12), this is also for pulseaudio users:
|
||||
Example 1, no sound:
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -f x11grab -s 1920x1200 -framerate 15 -i :0.0 -c:v libx264 -preset fast -pix_fmt yuv420p -s 1280x800 -threads 0 -f flv "rtmp://live.twitch.tv/app/live_********_******************************"
|
||||
</pre>
|
||||
<p>
|
||||
Example 2, first screen (on dual screen setup, or if on a single screen):
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -f x11grab -s 1920x1200 -framerate 15 -i :0.0 -f pulse -ac 2 -i default -c:v libx264 -preset fast -pix_fmt yuv420p -s 1280x800 -c:a aac -b:a 160k -ar 44100 -threads 0 -f flv "rtmp://live.twitch.tv/app/live_********_******************************"
|
||||
</pre>
|
||||
<p>
|
||||
Example 3, second screen (on dual screen setup):
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -f x11grab -s 1920x1200 -framerate 15 -i :0.0+1920,0 -f pulse -ac 2 -i default -c:v libx264 -preset fast -pix_fmt yuv420p -s 1280x800 -c:a aac -b:a 160k -ar 44100 -threads 0 -f flv "rtmp://live.twitch.tv/app/live_********_******************************"
|
||||
</pre>
|
||||
<h2 id="Latency">Latency<a class="anchor" href="#Latency" title="Link to this section"> ¶</a></h2>
|
||||
<p>
|
||||
You may be able to decrease initial "startup" latency by specifing that I-frames come "more frequently" (or
|
||||
basically always, in the case of <a class="wiki" href="/wiki/Encode/H.264">x264</a>'s zerolatency setting),
|
||||
though this can increase frame size and decrease quality, see <a class="ext-link" href="http://mewiki.project357.com/wiki/X264_Encoding_Suggestions"><span
|
||||
class="icon"></span>here</a> for some more background. Basically for typical x264 streams, it inserts an
|
||||
I-frame every 250 frames. This means that new clients that connect to the stream may have to wait up to 250
|
||||
frames before they can start receiving the stream (or start with old data). So increasing I-frame frequency
|
||||
(makes the stream larger, but might decrease latency). For real time captures you can also decrease latency of
|
||||
audio in windows dshow by using the dshow audio_buffer_size <a class="ext-link" href="http://ffmpeg.org/ffmpeg.html#Options"><span
|
||||
class="icon"></span>setting</a>. You can also decrease latency by tuning any broadcast server you are using to
|
||||
minimize latency, and finally by tuning the client that receives the stream to not "cache" any incoming data,
|
||||
which, if it does, increases latency.
|
||||
</p>
|
||||
<p>
|
||||
Sometimes audio codecs also introduce some latency of their own. You may be able to get less latency by using
|
||||
speex, for example, or opus, in place of libmp3lame.
|
||||
</p>
|
||||
<p>
|
||||
You will also want to try and decrease latency at the server side, for instance <a class="ext-link" href="http://www.wowza.com/forums/content.php?81-How-to-achieve-the-lowest-latency-from-capture-to-playback"><span
|
||||
class="icon"></span>wowza</a> hints.
|
||||
</p>
|
||||
<p>
|
||||
Also setting -probesize and -analyzeduration to low values may help your stream start up more quickly (it uses
|
||||
these to scan for "streams" in certain muxers, like ts, where some can appears "later", and also to estimate the
|
||||
duration, which, for live streams, the latter you don't need anyway). This should be unneeded by dshow input.
|
||||
</p>
|
||||
<p>
|
||||
Reducing cacheing at the client side can help, too, for instance mplayer has a "-nocache" option, other players
|
||||
may similarly has some type of pre-playback buffering that is occurring. (The reality is mplayers -benchmark
|
||||
option has much more effect).
|
||||
</p>
|
||||
<p>
|
||||
Using an encoder that encodes more quickly (or possibly even raw format?) might reduce latency.
|
||||
</p>
|
||||
<p>
|
||||
You might get less latency by using one of the "point to point" protocols described in this document, at well.
|
||||
You'd lose the benefit of having a server, of course.
|
||||
</p>
|
||||
<p>
|
||||
NB that a client when it initially starts up may have to wait until the next i-frame to be able to start
|
||||
receiving the stream (ex: if receiving UDP), so the GOP setting (-g) i-frame interval will have an effect on how
|
||||
quickly they can start streaming (i.e. they must receive an i-frame before they start). Setting it to a lower
|
||||
number means it will use more bandwidth, but clients will be able to connect more quickly (the default for x264
|
||||
is 250--so for 30 fps that means an i-frame only once every 10 seconds or so). So it's a tradeoff if you adjust
|
||||
it. This does not affect actual latency (just connection time) since the client can still display frames very
|
||||
quickly after and once it has received its first i-frame. Also if you're using a lossy transport, like UDP, then
|
||||
an i-frame represents "the next change it will have to repair the stream" if there are problems from packet loss.
|
||||
</p>
|
||||
<p>
|
||||
You can also (if capturing from a live source) increase frame rate to decrease latency (which affects throughput
|
||||
and also i-frame frequency, of course). This obvious sends packets more frequently, so (with 5 fps, you introduce
|
||||
at least a 0.2s latency, with 10 fps 0.1s latency) but it also helps clients to fill their internal buffers, etc.
|
||||
more quickly.
|
||||
</p>
|
||||
<p>
|
||||
Note also that using dshow's "rtbufsize" has the unfortunate side effect of sometimes allowing frames to "buffer"
|
||||
while it is waiting on encoding of previous frames, or waiting for them to be sent over the wire. This means that
|
||||
if you use a higher value at all, it can cause/introduce added latency if it ever gets used (but if used, can be
|
||||
helpful for other aspects, like transmitting more frames overall consistently etc. so YMMV). Almost certainly if
|
||||
you set a very large value for this, and then see the "buffer XX% full! dropping!" message, you are introducing
|
||||
latency.
|
||||
</p>
|
||||
<p>
|
||||
There is also apparently an option -fflags nobuffer which might possibly help, usually for receiving streams <a
|
||||
class="ext-link" href="https://www.ffmpeg.org/ffmpeg-formats.html#Format-Options"><span class="icon"></span>reduce
|
||||
latency</a>.
|
||||
</p>
|
||||
<div>
|
||||
<p>
|
||||
mpv udp://236.0.0.1:2000 --no-cache --untimed --no-demuxer-thread --video-sync=audio --vd-lavc-threads=1
|
||||
</p>
|
||||
<p>
|
||||
may be useful.
|
||||
</p>
|
||||
<h3 id="Testinglatency">Testing latency<a class="anchor" href="#Testinglatency" title="Link to this section"> ¶</a></h3>
|
||||
<p>
|
||||
By default, ffplay (as a receiver for testing latency) introduces significant latency of its own, so if you use
|
||||
it for testing (see troubleshooting section) it may not reflect latency accurately. FFplay introduces some video
|
||||
artifacts, also, see notes for it in "troubleshooting streaming" section Also some settings mentioned above like
|
||||
"probesize" might help it start more quickly. Also useful:
|
||||
</p>
|
||||
<pre class="wiki">ffplay -probesize 32 -sync ext INPUT
|
||||
</pre>
|
||||
<p>
|
||||
(the sync part tells it to try and stay realtime).
|
||||
</p>
|
||||
<p>
|
||||
Useful is mplayer with its -benchmark for testing latency (-noaudio and/or -nocache *might* be useful, though I
|
||||
haven't found -nocache to provide any latency benefit it might work for you).
|
||||
</p>
|
||||
<p>
|
||||
Using the SDL out option while using FFmpeg to receive the stream might also help to view frames with less
|
||||
client side latency: "ffmpeg ... -f sdl <input_here> "window title"" (this works especially well with
|
||||
-fflags nobuffer, though in my tests is still barely more latency than using mplayer -benchmark always). This
|
||||
doesn't have a "drop frames if you run out of cpu" option so it can get quite far behind at times (introduce
|
||||
more latency variably).
|
||||
</p>
|
||||
<p>
|
||||
Another possibly useful receiving client is "omxplayer -live"
|
||||
</p>
|
||||
<p>
|
||||
See also "Point to point streaming" section esp. if you use UDP etc.
|
||||
</p>
|
||||
<h3 id="Seealso">See also<a class="anchor" href="#Seealso" title="Link to this section"> ¶</a></h3>
|
||||
<p>
|
||||
<a class="ext-link" href="http://stackoverflow.com/a/12085571/32453"><span class="icon"></span>Here</a> is a
|
||||
list of some other ideas to try (using VBR may help, etc.)
|
||||
</p>
|
||||
<h2 id="CPUusageFilesize">CPU usage / File size<a class="anchor" href="#CPUusageFilesize" title="Link to this section">
|
||||
¶</a></h2>
|
||||
<p>
|
||||
In general, the more CPU you use to compress, the better the output image will be, or the smaller of a file the
|
||||
output will be for the same quality.
|
||||
</p>
|
||||
<p>
|
||||
Basically, the easiest way to save cpu is to decrease the input frame rate/size, or decrease the output frame
|
||||
rate/size.
|
||||
</p>
|
||||
<p>
|
||||
Also you could (if capturing from live source), instruct the live source to feed a "smaller stream" (ex: webcam
|
||||
stream 640x480 instead of 1024x1280), or you could set a lower output "output quality" setting (q level), or
|
||||
specify a lower output desired bitrate (see <a class="wiki" href="/wiki/Encode/H.264">Encode/H.264</a> for a
|
||||
background). Or try a different output codec, or specify new parameters to your codec (for instance, a different
|
||||
profile or preset for <a class="wiki" href="/wiki/Encode/H.264">libx264</a>). Specifying $ -threads 0 instructs
|
||||
the encoder to use all available cpu cores, which is the default. You could also resize the input first, before
|
||||
transcoding it, so it's not as large. Applying a smoothing filter like hqdn3d before encoding might help it
|
||||
compress better, yielding smaller files.
|
||||
</p>
|
||||
<p>
|
||||
You can also set a lower output frame rate to of course decrease cpu usage.
|
||||
</p>
|
||||
<p>
|
||||
If you're able to live capture in a pixel format that matches your output format (ex: yuv420p output from a
|
||||
webcam, instead of mjpeg), that might help with cpu usage, since it avoids an extra conversion. Using 64-bit
|
||||
instead of 32-bit executables (for those that have that choice) can result in a slight speedup. If you're able
|
||||
to use -vcodec copy that, of course, uses the least cpu of all options since it just sends the frames verbatim
|
||||
to the output.
|
||||
</p>
|
||||
<p>
|
||||
Sometimes you can change the "pixel formats" somehow, like using rgb16 instead of rgb24, to save time/space (or
|
||||
yuv420 instead of yuv444 or the like, since 420 stores less information it may compress better and use less
|
||||
bandwidth). This may not affect latency.
|
||||
</p>
|
||||
<h2 id="StreamingasimpleRTPaudiostreamfromFFmpeg">Streaming a simple RTP audio stream from FFmpeg<a class="anchor"
|
||||
href="#StreamingasimpleRTPaudiostreamfromFFmpeg" title="Link to this section"> ¶</a></h2>
|
||||
<p>
|
||||
FFmpeg can stream a single stream using the <a class="ext-link" href="http://en.wikipedia.org/wiki/Real-time_Transport_Protocol"><span
|
||||
class="icon"></span>RTP protocol</a>. In order to avoid buffering problems on the other hand, the streaming
|
||||
should be done through the -re option, which means that the stream will be streamed in real-time (i.e. it slows
|
||||
it down to simulate a live streaming <a class="ext-link" href="http://ffmpeg.org/ffmpeg.html"><span class="icon"></span>source</a>.
|
||||
</p>
|
||||
<p>
|
||||
For example the following command will generate a signal, and will stream it to the port 1234 on localhost:
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -re -f lavfi -i aevalsrc="sin(400*2*PI*t)" -ar 8000 -f mulaw -f rtp rtp://127.0.0.1:1234
|
||||
</pre>
|
||||
<p>
|
||||
To play the stream with ffplay (which has some caveats, see above), run the command:
|
||||
</p>
|
||||
<pre class="wiki">ffplay rtp://127.0.0.1:1234
|
||||
</pre>
|
||||
<p>
|
||||
Note that rtp by default uses UDP, which, for large streams, can cause packet loss. See the "point to point"
|
||||
section in this document for hints if this ever happens to you.
|
||||
</p>
|
||||
<h2 id="Codecs">Codecs<a class="anchor" href="#Codecs" title="Link to this section"> ¶</a></h2>
|
||||
<p>
|
||||
The most popular streaming codec is probably <a class="ext-link" href="http://www.videolan.org/developers/x264.html"><span
|
||||
class="icon"></span>libx264</a>, though if you're streaming to a device which requires a "crippled" baseline
|
||||
h264 implementation, you can use the x264 "baseline" profile. Some have have argued that the mp4 video codec is
|
||||
<a class="ext-link" href="http://forums.macrumors.com/showthread.php?t=398016"><span class="icon"></span>better</a>
|
||||
than x264 baseline, because it encodes about as well with less cpu. You may be able to use other codecs, like
|
||||
mpeg2video, or really any other video codec you want, typically, as long as your receiver can decode it, if it
|
||||
suits your needs.
|
||||
</p>
|
||||
<p>
|
||||
Also note that encoding it to the x264 "baseline" is basically a "compatibility mode" for older iOS devices or
|
||||
the like, see <a class="ext-link" href="http://sonnati.wordpress.com/2011/08/30/ffmpeg-%E2%80%93-the-swiss-army-knife-of-internet-streaming-%E2%80%93-part-iv/"><span
|
||||
class="icon"></span>here</a>.
|
||||
</p>
|
||||
<p>
|
||||
The mpeg4 video codec sometimes also comes "within a few percentage" of the compression of x264 "normal
|
||||
settings", but uses much less cpu to do the encoding. See <a class="ext-link" href="http://ffmpeg.zeranoe.com/forum/viewtopic.php?f=7&t=631&hilit=mpeg4+libx264+cores&start=10#p2163"><span
|
||||
class="icon"></span>http://ffmpeg.zeranoe.com/forum/viewtopic.php?f=7&t=631&hilit=mpeg4+libx264+cores&start=10#p2163</a>
|
||||
for some graphs (which may be slightly outdated). Basically in that particular test it was 54 fps to 58 fps
|
||||
(libx264 faster), and libx264 file was 5.1MB and mpeg4 was 6MB, but mpeg4 used only half as much cpu for its
|
||||
computation, so take it with a grain of salt.
|
||||
</p>
|
||||
<h2 id="HTTPLiveStreamingandStreamingwithmultiplebitrates">HTTP Live Streaming and Streaming with multiple
|
||||
bitrates<a class="anchor" href="#HTTPLiveStreamingandStreamingwithmultiplebitrates" title="Link to this section">
|
||||
¶</a></h2>
|
||||
<p>
|
||||
FFmpeg supports splitting files (using "-f segment" for the output, see <a class="ext-link" href="http://ffmpeg.org/ffmpeg.html#segment_002c-stream_005fsegment_002c-ssegment"><span
|
||||
class="icon"></span>segment muxer</a>) into time based chunks, useful for <a class="ext-link" href="http://en.wikipedia.org/wiki/HTTP_Live_Streaming"><span
|
||||
class="icon"></span>HTTP live streaming</a> style file output.
|
||||
</p>
|
||||
<p>
|
||||
How to stream with several different simultaneous bitrates is described <a class="ext-link" href="http://sonnati.wordpress.com/2011/08/30/ffmpeg-%E2%80%93-the-swiss-army-knife-of-internet-streaming-%E2%80%93-part-iv/"><span
|
||||
class="icon"></span>here</a>.
|
||||
</p>
|
||||
<p>
|
||||
See also <a class="ext-link" href="http://sonnati.wordpress.com/2012/07/02/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-v"><span
|
||||
class="icon"></span>http://sonnati.wordpress.com/2012/07/02/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-v</a>
|
||||
</p>
|
||||
<h2 id="SavingafileandStreamingatthesametime">Saving a file and Streaming at the same time<a class="anchor" href="#SavingafileandStreamingatthesametime"
|
||||
title="Link to this section"> ¶</a></h2>
|
||||
<p>
|
||||
See <a class="wiki" href="/wiki/Creating%20multiple%20outputs">Creating multiple outputs</a>. Basically, you may
|
||||
only be able to accept from a webcam or some other source from at most one process, in this case you'll need to
|
||||
"split" your output if you want to save it and stream it simultaneously. Streaming and saving simultaneously
|
||||
(and only encoding once) can also save cpu.
|
||||
</p>
|
||||
<h2 id="Transcodingrepeating">Transcoding / repeating<a class="anchor" href="#Transcodingrepeating" title="Link to this section">
|
||||
¶</a></h2>
|
||||
<p>
|
||||
FFmpeg can also receive from "a source" (for instance live or UDP) and then transcode and re-broadcast the
|
||||
stream.
|
||||
</p>
|
||||
<p>
|
||||
One mailing list user wrote this, quote:
|
||||
</p>
|
||||
<blockquote>
|
||||
<p>
|
||||
In my application, I have a server running vlc sucking streams from some cameras, encoding them as MPEG2
|
||||
streams and sending them to ports 5000 through 5003 on my intermediary server (I'll have to let someone else
|
||||
explain how to set that up, as that was someone else's part of the project). I have another server running
|
||||
Wowza, with an instance named "live". And I've got an intermediate server that sucks in MPEG2 streams coming in
|
||||
on ports 5000 to 5003, transcoding them into mp4 streams with H.264 and AAC codecs, and pushing the transcoded
|
||||
streams on to Wowza.
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
The command line I use to pull the stream from port 5000, transcode it, and push it is:
|
||||
ffmpeg -i 'udp://localhost:5000?fifo_size=1000000&overrun_nonfatal=1' -crf 30 -preset ultrafast -acodec aac
|
||||
-strict experimental -ar 44100 -ac 2 -b:a 96k -vcodec libx264 -r 25 -b:v 500k -f flv 'rtmp://<wowza server
|
||||
IP>/live/cam0'
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
-i 'udp://localhost:5000?fifo_size=1000000&overrun_nonfatal=1' tells ffmpeg where to pull the input stream
|
||||
from. The parts after the ? are probably not needed most of the time, but I did need it after all.
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
-crf 30 sets the Content Rate Factor. That's an x264 argument that tries to keep reasonably consistent video
|
||||
quality, while varying bitrate during more 'complicated' scenes, etc. A value of 30 allows somewhat lower
|
||||
quality and bit rate. See <a class="wiki" href="/wiki/Encode/H.264">Encode/H.264</a>.
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
-preset ultrafast as the name implies provides for the fastest possible encoding. If some tradeoff between
|
||||
quality and encode speed, go for the speed. This might be needed if you are going to be transcoding multiple
|
||||
streams on one machine.
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
-acodec aac sets the audio codec (internal AAC encoder)
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
-strict experimental allows use of some experimental codecs (the internal AAC encoder is experimental)
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
-ar 44100 set the audio sample rate
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
-ac 2 specifies two channels of audio
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
-b:a 96k sets the audio bit rate
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
-vcodec libx264 sets the video codec
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
-r 25 set the frame rate
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
-b:v 500k set the video bit rate
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
-f flv says to deliver the output stream in an flv wrapper
|
||||
</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>
|
||||
'rtmp://<wowza server IP>/live/cam0' is where the transcoded video stream gets pushed to
|
||||
</p>
|
||||
</blockquote>
|
||||
<h2 id="Adjustingbitratebasedonlineconditions">Adjusting bitrate based on line conditions<a class="anchor" href="#Adjustingbitratebasedonlineconditions"
|
||||
title="Link to this section"> ¶</a></h2>
|
||||
<p>
|
||||
FFmpeg doesn't (today) support varying the encoding bitrate based on fluctuating network conditions. It does
|
||||
support outputting in several "different" fixed bitrates, at the same time, however, see "Streaming with
|
||||
multiple bitrates" on this page, which is vaguely related. Also if you are during direct capture from
|
||||
directshow, the input device starts dropping frames when there is congestion, which somewhat simulates a
|
||||
variable outgoing bitrate.
|
||||
</p>
|
||||
<h2 id="TroubleshootingStreaming">Troubleshooting Streaming<a class="anchor" href="#TroubleshootingStreaming"
|
||||
title="Link to this section"> ¶</a></h2>
|
||||
<p>
|
||||
If you get a "black/blank" screen in the client, try sending it yuv422p or yuv420p type input. Some servers get
|
||||
confused if you send them yuv444 input (which is the default for libx264).
|
||||
</p>
|
||||
<p>
|
||||
NB that when you are testing your stream settings, you may want to test them with both VLC and <a class="ext-link"
|
||||
href="http://ffmpeg.org/ffplay.html"><span class="icon"></span>FFplay</a>, as FFplay sometimes introduces its
|
||||
own artifacts when it is scaled (FFplay uses poor quality default scaling, which can be inaccurate). Don't use
|
||||
ffplay as your baseline for determining quality.
|
||||
</p>
|
||||
<h2 id="Pointtopointstreaming">Point to point streaming<a class="anchor" href="#Pointtopointstreaming" title="Link to this section">
|
||||
¶</a></h2>
|
||||
<p>
|
||||
If you want to stream "from one computer to another", you could start up a server on one, and then stream from
|
||||
FFmpeg to that server, then have the client connect to that server (server could either be on client or server
|
||||
side computers). Or you could do a point to point type stream, like:
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -i INPUT -acodec libmp3lame -ar 11025 --f rtp rtp://host:port
|
||||
</pre>
|
||||
<p>
|
||||
where host is the receiving IP. Then receive the stream using VLC or ffmpeg from that port (since rtp uses UDP,
|
||||
the receiver can start up any time).
|
||||
</p>
|
||||
<p>
|
||||
or
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -i INPUT -f mpegts udp://host:port
|
||||
</pre>
|
||||
<p>
|
||||
If you run into packet loss (green frames, tearing/shearing--since UDP is not guaranteed delivery, this can
|
||||
occur) first make sure your FFmpeg is compiled with pthreads support enabled (if it is, then it uses a separate
|
||||
thread to receive from the UDP port, which can cause less packet loss). You can tell that it is by specifying a
|
||||
url like udp://host:post?fifo_size=10000 (if it accepts fifo_size, then you're good to go). Similarly, for
|
||||
mplayer, you can use mplayer ffmpeg://udp://host:port?fifo_size=XXX for possibly better results on the receiving
|
||||
end (mplayer needs a patch first, email <a class="mail-link" href="mailto:rogerdpack@gmail.com"><span class="icon"></span>rogerdpack@gmail.com</a>
|
||||
for it, once was <a class="ext-link" href="https://gist.githubusercontent.com/rdp/9075572/raw/002dc1b745c895693fdb160cc9be77ef31f75531/possible_mplayer_udp_fix.diff"><span
|
||||
class="icon"></span>https://gist.githubusercontent.com/rdp/9075572/raw/002dc1b745c895693fdb160cc9be77ef31f75531/possible_mplayer_udp_fix.diff</a>
|
||||
hackey work around patch, applied with ffmpeg subdir).
|
||||
</p>
|
||||
<p>
|
||||
Alternatively, increase your buffer size, like mplayer ffmpeg://udp://host:port?buffer_size=10000000 (the
|
||||
default is system dependent and typically far too low for any reasonable buffering. On linux though you can only
|
||||
set it to like 200K max anyway, so this isn't good enough--make sure to use the circular buffer, and that the
|
||||
following works: ffmpeg://udp://host:port?buffer_size=10000000?fifo_size=100000 (the fifo_size should not emit a
|
||||
warning, and implies that you have a secondary thread that collects incoming packets for you if there is no
|
||||
warning).
|
||||
</p>
|
||||
<p>
|
||||
Another option is to use some transmission type that uses TCP for your transport. (The RTMP protocol, popular in
|
||||
streaming to servers, uses TCP probably for this reason--you just can't use that for point to point streaming).
|
||||
</p>
|
||||
<p>
|
||||
One option to use TCP is like this:
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -i INPUT -f mpegts tcp://host:port
|
||||
</pre>
|
||||
<p>
|
||||
which I would guess will try and (as a client) establish a connection do that host on that port (assuming it has
|
||||
a server waiting for the incoming connection). You could receive it like this:
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -i tcp://local_hostname:port?listen
|
||||
</pre>
|
||||
<p>
|
||||
(basically, one side needs to specify "listen" and the other needs to not to).
|
||||
</p>
|
||||
<p>
|
||||
To use with mplayer as a receiver it would be like
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -i ... -f mpegts "tcp://127.0.0.1:2000"
|
||||
</pre>
|
||||
<p>
|
||||
and on the mplayer side
|
||||
</p>
|
||||
<pre class="wiki">mplayer ... ffmpeg://tcp://127.0.0.1:2000?listen
|
||||
</pre>
|
||||
<p>
|
||||
(start mplayer first)
|
||||
</p>
|
||||
<p>
|
||||
Another option is to use RTP (which by default uses UDP) but by specifying it use TCP:
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -i input -f rtsp -rtsp_transport tcp rtsp://localhost:8888/live.sdp
|
||||
</pre>
|
||||
<p>
|
||||
(For meanings of options see <a class="ext-link" href="http://ffmpeg.org/ffmpeg-protocols.html#rtsp"><span class="icon"></span>official
|
||||
documentation</a>.
|
||||
</p>
|
||||
<p>
|
||||
Then you may receive it like this (ffplay or ffmpeg):
|
||||
</p>
|
||||
<pre class="wiki">ffplay -rtsp_flags listen rtsp://localhost:8888/live.sdp?tcp
|
||||
# ending "?tcp" may not be needed -- you will need to start the server up first, before the sending client
|
||||
</pre>
|
||||
<p>
|
||||
ffmpeg also has a "listen" option for rtmp so it may be able to receive a "straight" rtmp streams from a single
|
||||
client that way.
|
||||
</p>
|
||||
<p>
|
||||
With tcp based streams you can probably use any formatting/muxer, but with udp you need to be careful and use a
|
||||
muxer that supports 'connecting anytime' like mpegts.
|
||||
</p>
|
||||
<p>
|
||||
If you are forced to use udp (for instance you need to broadcast to a multicast port for whatever reason) then
|
||||
you may be able to avoid the packet loss by (sending less data or sending the same frames over and over again so
|
||||
they have a higher chance of being received).
|
||||
</p>
|
||||
<p>
|
||||
See also the section on i-frames in <a class="wiki" href="/wiki/StreamingGuide#Latency">#Latency</a>.
|
||||
</p>
|
||||
<p>
|
||||
Final working p2p client, with multicast:
|
||||
</p>
|
||||
<p>
|
||||
server:
|
||||
</p>
|
||||
<pre class="wiki">ffmpeg -f dshow -framerate 20 -i video=screen-capture-recorder -vf scale=1280:720 -vcodec libx264 -pix_fmt yuv420p -tune zerolatency -preset ultrafast -f mpegts udp://236.0.0.1:2000
|
||||
</pre>
|
||||
<p>
|
||||
client:
|
||||
</p>
|
||||
<pre class="wiki">mplayer -demuxer +mpegts -framedrop -benchmark ffmpeg://udp://236.0.0.1:2000?fifo_size=100000&buffer_size=10000000
|
||||
# (see note above about linux needing fifo_size as well as buffer_size).
|
||||
</pre>
|
||||
<h2 id="Externallinks">External links<a class="anchor" href="#Externallinks" title="Link to this section"> ¶</a></h2>
|
||||
<ul>
|
||||
<li><a class="ext-link" href="http://sonnati.wordpress.com/2011/08/30/ffmpeg-–-the-swiss-army-knife-of-internet-streaming-–-part-iv/"><span
|
||||
class="icon"></span>Fabio Sonnati's tutorial on streaming with FFmpeg</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="trac-modifiedby">
|
||||
<span><a href="/wiki/StreamingGuide?action=diff&version=82" title="Version 82 by rogerdpack">Last modified</a>
|
||||
<a class="timeline" href="/timeline?from=2018-08-30T01%3A49%3A59%2B03%3A00&precision=second" title="See timeline at Aug 30, 2018, 1:49:59 AM">5
|
||||
months ago</a></span>
|
||||
<span class="trac-print">Last modified on Aug 30, 2018, 1:49:59 AM</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div id="altlinks">
|
||||
<h3>Download in other formats:</h3>
|
||||
<ul>
|
||||
<li class="last first">
|
||||
<a rel="nofollow" href="/wiki/StreamingGuide?format=txt">Plain Text</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer" lang="en" xml:lang="en">
|
||||
<hr>
|
||||
<a id="tracpowered" href="http://trac.edgewall.org/"><img src="/chrome/common/trac_logo_mini.png" height="30" width="107"
|
||||
alt="Trac Powered"></a>
|
||||
<p class="left">Powered by <a href="/about"><strong>Trac 1.0.1</strong></a><br>
|
||||
By <a href="http://www.edgewall.org/">Edgewall Software</a>.</p>
|
||||
<p class="right">Visit the Trac open source project at<br><a href="http://trac.edgewall.org/">http://trac.edgewall.org/</a></p>
|
||||
</div>
|
||||
|
||||
<div style="background-color: rgb(255, 143, 0); display: none; color: white; text-align: center; position: fixed; top: 0px; left: 0px; width: 100%; height: auto; min-width: 100%; min-height: auto; max-width: 100%; font: 12px "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; cursor: pointer; padding: 5px;"><span
|
||||
style="color: white; font: 12px "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;">You have turned
|
||||
off the paragraph player. You can turn it on again from the options page.</span><img src="chrome-extension://gfjopfpjmkcfgjpogepmdjmcnihfpokn/img/icons/icon-close_16.png"
|
||||
style="width: 20px; height: auto; min-width: 20px; min-height: auto; max-width: 20px; float: right; margin-right: 10px;"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
395
test/html/gmail.html
Normal file
395
test/html/gmail.html
Normal file
File diff suppressed because one or more lines are too long
9
test/html/google.html
Normal file
9
test/html/google.html
Normal file
File diff suppressed because one or more lines are too long
588
test/html/hillcrestpartyrentals.html
Normal file
588
test/html/hillcrestpartyrentals.html
Normal file
|
|
@ -0,0 +1,588 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="Content-Language" content="en-us">
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<title>Hillcrest Party Rentals - Offering Tents, Chairs, Tables, Lighting, Linens, China, Glassware, Silverware,
|
||||
Trays, Heaters and more for your Events, Weddings and Other Occasions</title>
|
||||
<META NAME="DESCRIPTION" CONTENT="HillCrest Party Rental supplies qualtiy and affordable party rental supplies and equipment such as Tables, chairs,
|
||||
linens, China, Glasses, Silverware, Trays, Heaters, Bars, Wedding equipment, catering equipment tents and more for all types of events, parties, Office events
|
||||
Weddings and more. Services the Los Angeles Area incuding Beverly Hills, Malibu, Bel Air, Culver City, West Los Angeles, Santa Monica, Manhattan Beach, Redondo
|
||||
Beach, South Bay, Marina Del Rey, Playa Del Rey,Pacific Palisades, Encino ,Tarzana, Sherman Oaks, Studio City Pasadena, San Marino, Cheviot Hills, Westlake Village, Valley Village, Westchester, Playa Vista, Del Rey, Venice.">
|
||||
<META NAME="KEYWORDS" CONTENT="Party Rentals,Tents,Chairs, Tables,Lighting,Linens,China,Glassware,Silverware,Trays,Heaters,Bars,Wedding Equipment, Office Party, Evets, Social Events, Community Events, Catering Equipment and supplies ">
|
||||
<META NAME="AUTHOR" CONTENT="Kt Designing Inc">
|
||||
<META NAME="RESOURCE-TYPE" CONTENT="DOCUMENT">
|
||||
<META NAME="DISTRIBUTION" CONTENT="GLOBAL">
|
||||
<META NAME="ROBOTS" CONTENT="INDEX, FOLLOW">
|
||||
<META NAME="REVISIT-AFTER" CONTENT="1 DAYS">
|
||||
<META NAME="RATING" CONTENT="GENERAL">
|
||||
<script language="JavaScript" fptype="dynamicanimation">
|
||||
< !--
|
||||
function dynAnimation() { }
|
||||
function clickSwapImg() { }
|
||||
//-->
|
||||
</script>
|
||||
<script language="JavaScript1.2" fptype="dynamicanimation" src="animate.js">
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body topmargin="0" leftmargin="0" onload="dynAnimation()">
|
||||
|
||||
<div align="center">
|
||||
<center>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="100%"
|
||||
id="AutoNumber1">
|
||||
<tr>
|
||||
<td width="100%" bgcolor="#DE0404">
|
||||
<div align="center">
|
||||
<center>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="1000"
|
||||
id="AutoNumber2" bgcolor="#DE0404" height="11">
|
||||
<tr>
|
||||
<td width="110" height="11">
|
||||
<p align="right">
|
||||
<img border="0" src="Images/red_bar_left.jpg" width="24" height="22">
|
||||
</td>
|
||||
<td width="782" height="11">
|
||||
<p align="center">
|
||||
<font face="Arial" color="#FFFFFF" size="2">Your
|
||||
#1 Party Rental source for all Outdoor Events, Tents & Canopies,
|
||||
<a href="Los_Angeles_Wedding_Party_Rentals.html">
|
||||
<font color="#FFFFFF">Weddings</font>
|
||||
</a>, Cateting Equipment, Lighting and more!</font>
|
||||
</td>
|
||||
<td width="108" height="11">
|
||||
<img border="0" src="Images/red_bar_right.jpg" width="24" height="22"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%">
|
||||
<div align="center">
|
||||
<center>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="1000"
|
||||
id="AutoNumber3" height="239">
|
||||
<tr>
|
||||
<td width="110" height="93" background="Images/shade_left.jpg"> </td>
|
||||
<td width="782" height="93">
|
||||
<div align="center">
|
||||
<center>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111"
|
||||
width="100%" id="AutoNumber8" height="88">
|
||||
<tr>
|
||||
<td width="40%" height="88">
|
||||
<p align="center">
|
||||
<a href="index.html">
|
||||
<img border="0" src="Images/logo.jpg" alt="HillCrest Party Rentals" width="247" height="134"></a>
|
||||
</td>
|
||||
<td width="60%" height="90" valign="top">
|
||||
<div align="center">
|
||||
<center>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111"
|
||||
width="100%" id="AutoNumber9" height="136">
|
||||
<tr>
|
||||
<td width="100%" height="19" colspan="5"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="17" colspan="5">
|
||||
<p align="right"><i><span style="font-size: 11pt">Want
|
||||
to plan an event or have questions? Call Now!
|
||||
</span></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="28" colspan="5">
|
||||
<p align="right">
|
||||
<img border="0" src="Images/phone_number.jpg" width="221" height="28">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="19" colspan="5">
|
||||
<p align="right">
|
||||
<font face="Arial" style="font-size: 11pt">3507 Motor Avenue
|
||||
Los Angeles, CA 90034
|
||||
</font>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" height="22" colspan="5">
|
||||
<p align="center">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="29%" height="31">
|
||||
<p align="center">
|
||||
</td>
|
||||
<td width="19%" height="31">
|
||||
<p align="center">
|
||||
<a onmouseover="var img=document['fpAnimswapImgFP2'];img.imgRolln=img.src;img.src=img.lowsrc?img.lowsrc:img.getAttribute?img.getAttribute('lowsrc'):img.src;"
|
||||
onmouseout="document['fpAnimswapImgFP2'].src=document['fpAnimswapImgFP2'].imgRolln" href="index.html">
|
||||
<img border="0" src="Images/link_home.jpg" id="fpAnimswapImgFP2" name="fpAnimswapImgFP2"
|
||||
dynamicanimation="fpAnimswapImgFP2" lowsrc="Images/link_home.jpg" width="52" height="16"></a>
|
||||
</td>
|
||||
<td width="19%" height="31">
|
||||
<p align="center">
|
||||
<a onmouseover="var img=document['fpAnimswapImgFP3'];img.imgRolln=img.src;img.src=img.lowsrc?img.lowsrc:img.getAttribute?img.getAttribute('lowsrc'):img.src;"
|
||||
onmouseout="document['fpAnimswapImgFP3'].src=document['fpAnimswapImgFP3'].imgRolln" href="aboutus.html">
|
||||
<img border="0" src="Images/link_aboutus.jpg" id="fpAnimswapImgFP3" name="fpAnimswapImgFP3"
|
||||
dynamicanimation="fpAnimswapImgFP3" lowsrc="Images/link_aboutusi.jpg" width="75" height="16"></a>
|
||||
</td>
|
||||
<td width="23%" height="31">
|
||||
<p align="center">
|
||||
<a onmouseover="var img=document['fpAnimswapImgFP4'];img.imgRolln=img.src;img.src=img.lowsrc?img.lowsrc:img.getAttribute?img.getAttribute('lowsrc'):img.src;"
|
||||
onmouseout="document['fpAnimswapImgFP4'].src=document['fpAnimswapImgFP4'].imgRolln" href="contactus.html">
|
||||
<img border="0" src="Images/link_contactus.jpg" id="fpAnimswapImgFP4" name="fpAnimswapImgFP4"
|
||||
dynamicanimation="fpAnimswapImgFP4" lowsrc="Images/link_contactusi.jpg" width="90" height="16"></a>
|
||||
</td>
|
||||
<td width="10%" height="31"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</div>
|
||||
</td>
|
||||
<td width="108" height="93" background="Images/shade_right.jpg">
|
||||
<img border="0" src="Images/side.jpg" width="53" height="92"><br>
|
||||
<span style="font-size: 7pt"> <br>
|
||||
</span> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="110" height="19" background="Images/shade_left.jpg"> </td>
|
||||
<td width="782" height="19">
|
||||
<div align="center">
|
||||
<center>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111"
|
||||
width="565" id="AutoNumber10">
|
||||
<tr>
|
||||
<td width="420" rowspan="4">
|
||||
<img border="0" src="Images/Hp_photo.jpg" width="458" height="281"></td>
|
||||
<td width="145">
|
||||
<a onmouseover="var img=document['fpAnimswapImgFP5'];img.imgRolln=img.src;img.src=img.lowsrc?img.lowsrc:img.getAttribute?img.getAttribute('lowsrc'):img.src;"
|
||||
onmouseout="document['fpAnimswapImgFP5'].src=document['fpAnimswapImgFP5'].imgRolln" href="services.html">
|
||||
<img border="0" src="Images/bannerlink1.jpg" id="fpAnimswapImgFP5" name="fpAnimswapImgFP5"
|
||||
dynamicanimation="fpAnimswapImgFP5" lowsrc="Images/bannerlink1i.jpg" width="218" height="79"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="145">
|
||||
<a onmouseover="var img=document['fpAnimswapImgFP8'];img.imgRolln=img.src;img.src=img.lowsrc?img.lowsrc:img.getAttribute?img.getAttribute('lowsrc'):img.src;"
|
||||
onmouseout="document['fpAnimswapImgFP8'].src=document['fpAnimswapImgFP8'].imgRolln" href="product_catalog.html">
|
||||
<img border="0" src="Images/bannerlink2.jpg" id="fpAnimswapImgFP8" name="fpAnimswapImgFP8"
|
||||
dynamicanimation="fpAnimswapImgFP8" lowsrc="Images/bannerlink2i.jpg" width="218" height="62"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="145">
|
||||
<a onmouseover="var img=document['fpAnimswapImgFP6'];img.imgRolln=img.src;img.src=img.lowsrc?img.lowsrc:img.getAttribute?img.getAttribute('lowsrc'):img.src;"
|
||||
onmouseout="document['fpAnimswapImgFP6'].src=document['fpAnimswapImgFP6'].imgRolln" href="gallery.html">
|
||||
<img border="0" src="Images/bannerlink3.jpg" id="fpAnimswapImgFP6" name="fpAnimswapImgFP6"
|
||||
dynamicanimation="fpAnimswapImgFP6" lowsrc="Images/bannerlink3i.jpg" width="218" height="61"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="145">
|
||||
<a onmouseover="var img=document['fpAnimswapImgFP7'];img.imgRolln=img.src;img.src=img.lowsrc?img.lowsrc:img.getAttribute?img.getAttribute('lowsrc'):img.src;"
|
||||
onmouseout="document['fpAnimswapImgFP7'].src=document['fpAnimswapImgFP7'].imgRolln" href="servicerequest.html">
|
||||
<img border="0" src="Images/bannerlink4.jpg" id="fpAnimswapImgFP7" name="fpAnimswapImgFP7"
|
||||
dynamicanimation="fpAnimswapImgFP7" lowsrc="Images/bannerlink4i.jpg" width="218" height="80"></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</div>
|
||||
</td>
|
||||
<td width="108" height="19" background="Images/shade_right.jpg"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="110" height="146" background="Images/shade_left.jpg"> </td>
|
||||
<td width="782" height="146" valign="top">
|
||||
<div align="center">
|
||||
<center>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111"
|
||||
width="674" id="AutoNumber11" height="576">
|
||||
<tr>
|
||||
<td width="674" height="19" colspan="3"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="412" height="31" colspan="2">
|
||||
<img border="0" src="Images/hp_title.jpg" width="358" height="76"></td>
|
||||
<td width="262" height="142" rowspan="2" valign="top">
|
||||
<div align="center">
|
||||
<center>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111"
|
||||
width="100%" id="AutoNumber12" height="242">
|
||||
<tr>
|
||||
<td width="100%" colspan="4" height="19"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" colspan="4" height="19">Our PRODUCT
|
||||
CATALOG<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="5%" height="20"> </td>
|
||||
<td width="7%" height="20" align="center" valign="middle">
|
||||
<img border="0" src="Images/bullet_points2.jpg" width="7" height="7"></td>
|
||||
<td width="88%" height="20" colspan="2">
|
||||
<font face="Arial" style="font-size: 11pt; text-decoration:underline" color="#DE0404">
|
||||
<a href="product_catalog2.html">
|
||||
<font color="#AC0505">Chairs</font>
|
||||
</a></font>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="5%" height="20"> </td>
|
||||
<td width="7%" height="20" align="center" valign="middle">
|
||||
<img border="0" src="Images/bullet_points2.jpg" width="7" height="7"></td>
|
||||
<td width="88%" height="20" colspan="2">
|
||||
<font face="Arial" style="font-size: 11pt; text-decoration:underline" color="#DE0404">
|
||||
<a href="product_catalog.html">
|
||||
<font color="#AC0505">Tables</font>
|
||||
</a></font>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="5%" height="21"> </td>
|
||||
<td width="7%" height="21" align="center" valign="middle">
|
||||
<img border="0" src="Images/bullet_points2.jpg" width="7" height="7"></td>
|
||||
<td width="88%" height="21" colspan="2">
|
||||
<font face="Arial" style="font-size: 11pt; text-decoration:underline" color="#DE0404">
|
||||
<a href="product_catalog3.html">
|
||||
<font color="#AC0505">Linens &
|
||||
Napkins</font>
|
||||
</a></font>
|
||||
<font face="Arial" style="font-size: 11pt; text-decoration:underline" color="#AC0505"> </font>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="5%" height="21"> </td>
|
||||
<td width="7%" height="21" align="center" valign="middle">
|
||||
<img border="0" src="Images/bullet_points2.jpg" width="7" height="7"></td>
|
||||
<td width="88%" height="21" colspan="2">
|
||||
<font face="Arial" style="font-size: 11pt; text-decoration:underline" color="#DE0404">
|
||||
<a href="product_catalog4.html">
|
||||
<font color="#AC0505">China &
|
||||
Chargers</font>
|
||||
</a></font>
|
||||
<font face="Arial" style="font-size: 11pt; text-decoration:underline" color="#AC0505"> </font>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="5%" height="21"> </td>
|
||||
<td width="7%" height="21" align="center" valign="middle">
|
||||
<img border="0" src="Images/bullet_points2.jpg" width="7" height="7"></td>
|
||||
<td width="88%" height="21" colspan="2">
|
||||
<font face="Arial" style="font-size: 11pt; text-decoration:underline" color="#DE0404">
|
||||
<a href="product_catalog6.html">
|
||||
<font color="#AC0505">Glassware</font>
|
||||
</a></font>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="5%" height="21"> </td>
|
||||
<td width="7%" height="21" align="center" valign="middle">
|
||||
<img border="0" src="Images/bullet_points2.jpg" width="7" height="7"></td>
|
||||
<td width="88%" height="21" colspan="2">
|
||||
<font face="Arial" style="font-size: 11pt; text-decoration:underline" color="#DE0404">
|
||||
<a href="product_catalog7.html">
|
||||
<font color="#AC0505">Flatware</font>
|
||||
</a></font>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="5%" height="21"> </td>
|
||||
<td width="7%" height="21" align="center" valign="middle">
|
||||
<img border="0" src="Images/bullet_points2.jpg" width="7" height="7"></td>
|
||||
<td width="88%" height="21" colspan="2">
|
||||
<font face="Arial" style="font-size: 11pt; text-decoration:underline" color="#DE0404">
|
||||
<a href="product_catalog12.html">
|
||||
<font color="#AC0505">Buffett &
|
||||
Serving</font>
|
||||
</a></font>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="5%" height="21"> </td>
|
||||
<td width="7%" height="21" align="center" valign="middle">
|
||||
<img border="0" src="Images/bullet_points2.jpg" width="7" height="7"></td>
|
||||
<td width="88%" height="21" colspan="2">
|
||||
<font face="Arial" style="font-size: 11pt; text-decoration:underline" color="#DE0404">
|
||||
<a href="product_catalog13.html">
|
||||
<font color="#AC0505">Wedding &
|
||||
Garden</font>
|
||||
</a></font>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="5%" height="38"> </td>
|
||||
<td width="7%" height="38"> </td>
|
||||
<td width="44%" height="38"> </td>
|
||||
<td width="44%" height="38">
|
||||
<img border="0" src="Images/bullet_points3.jpg" width="10" height="9">
|
||||
<u>
|
||||
<font size="2"><a href="product_catalog.html">
|
||||
<font color="#AC0505">Full Catalog</font>
|
||||
</a></font>
|
||||
</u></td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="393" height="111">
|
||||
<p style="margin-left: 25; margin-right: 15">
|
||||
<font face="Arial" style="font-size: 11pt">...serving Los
|
||||
Angeles area since 1984 from their facility located at Motor
|
||||
Ave and Palms Ave. HillCrest Party Rentals is a family owned business originating
|
||||
with Irving Sanshuck with business experience, Marilyn
|
||||
Sanshuck owner of The Paper Rainbow and Dorothy Levinson had a
|
||||
successful greeting card company and Bernie Sanshuck, manager
|
||||
and customer service at D&B Card Company. <br>
|
||||
<br>
|
||||
<img border="0" src="Images/bullet_points1.jpg" width="9" height="7">
|
||||
<font color="#DE0404"><u><a href="aboutus.html">
|
||||
<font color="#DE0404">more About US</font>
|
||||
</a></u></font>
|
||||
</font>
|
||||
</td>
|
||||
<td width="19" height="111"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="674" height="42" colspan="3">
|
||||
<p align="center">
|
||||
<img border="0" src="Images/line1.jpg" width="642" height="2">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="674" height="28" colspan="3">We have the PARTY RENTALS for all
|
||||
Events & Occasions!</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="674" height="180" colspan="3" bgcolor="#F5F5F5">
|
||||
<p align="center">
|
||||
<img border="0" src="Images/hp_photo1.jpg" width="129" height="180">
|
||||
<img border="0" src="Images/hp_photo2.jpg" width="129" height="180">
|
||||
<img border="0" src="Images/hp_photo3.jpg" width="129" height="180">
|
||||
<img border="0" src="Images/hp_photo4.jpg" width="129" height="180">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="674" height="19" colspan="3"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="674" height="30" colspan="3" bgcolor="#AC0505"> <b>
|
||||
<font face="Trajan Pro">Providing
|
||||
Party Rental Supplies to the city of Los Angeles Including:</font>
|
||||
</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="674" height="164" colspan="3" bgcolor="#DE0404">
|
||||
<div align="center">
|
||||
<center>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111"
|
||||
width="661" id="AutoNumber13" height="126">
|
||||
<tr>
|
||||
<td width="130" height="126">
|
||||
<p style="line-height: 150%; margin-left: 5">
|
||||
<font face="Arial" color="#FFFFFF" style="font-size: 11pt">
|
||||
Beverly Hills<br>
|
||||
Malibu<br>
|
||||
Bel Air<br>
|
||||
Culver City<br>
|
||||
West Los Angeles</font>
|
||||
</td>
|
||||
<td width="8" height="126">
|
||||
<p align="center">
|
||||
<img border="0" src="Images/line2.jpg" width="1" height="131">
|
||||
</td>
|
||||
<td width="130" height="126">
|
||||
<p style="line-height: 150%; margin-left: 5">
|
||||
<font face="Arial" color="#FFFFFF" style="font-size: 11pt">
|
||||
Santa Monica<br>
|
||||
Manhattan Beach<br>
|
||||
Redondo Beach<br>
|
||||
South Bay <br>
|
||||
Marina Del Rey</font>
|
||||
</td>
|
||||
<td width="5" height="126">
|
||||
<p align="center">
|
||||
<img border="0" src="Images/line2.jpg" width="1" height="131">
|
||||
</td>
|
||||
<td width="126" height="126">
|
||||
<p style="line-height: 150%; margin-left: 5">
|
||||
<font face="Arial" color="#FFFFFF" style="font-size: 11pt">
|
||||
Playa Del Rey<br>
|
||||
Pacific Palisades<br>
|
||||
Encino <br>
|
||||
Tarzana<br>
|
||||
Sherman Oaks </font>
|
||||
</td>
|
||||
<td width="5" height="126">
|
||||
<p align="center">
|
||||
<img border="0" src="Images/line2.jpg" width="1" height="131">
|
||||
</td>
|
||||
<td width="126" height="126">
|
||||
<p style="line-height: 150%; margin-left: 5">
|
||||
<font face="Arial" color="#FFFFFF" style="font-size: 11pt">
|
||||
Studio City Pasadena <br>
|
||||
San Marino <br>
|
||||
Cheviot Hills<br>
|
||||
Westlake Village</font>
|
||||
</td>
|
||||
<td width="4" height="126">
|
||||
<p align="center">
|
||||
<img border="0" src="Images/line2.jpg" width="1" height="131">
|
||||
</td>
|
||||
<td width="127" height="126">
|
||||
<p style="line-height: 150%; margin-left: 5">
|
||||
<font face="Arial" color="#FFFFFF" style="font-size: 11pt">
|
||||
Valley Village<br>
|
||||
Westchester <br>
|
||||
Playa Vista <br>
|
||||
Del Rey <br>
|
||||
Venice </font>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="674" height="9" colspan="3"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="674" height="32" colspan="3">
|
||||
<div align="center">
|
||||
<center>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111"
|
||||
width="100%" id="AutoNumber15">
|
||||
<tr>
|
||||
<td width="90%">
|
||||
<p align="right">
|
||||
<font size="2">Follow us:
|
||||
</font>
|
||||
</td>
|
||||
<td width="10%" valign="bottom">
|
||||
<a target="_blank" href="https://www.facebook.com/Hillcrestpartyrentals">
|
||||
<img border="0" src="Images/facebook.jpg" width="35" height="37"></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="674" height="34" colspan="3">
|
||||
<div align="center">
|
||||
<center>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111"
|
||||
width="100%" id="AutoNumber16" align="left" height="99">
|
||||
<tr>
|
||||
<td width="33%" height="38">
|
||||
<p align="center">
|
||||
<a href="index.html" style="text-decoration: none">
|
||||
<font color="#000000">LA Party Rentals</font>
|
||||
</a>
|
||||
</td>
|
||||
<td width="33%" align="center" height="38">
|
||||
<a href="Los_Angeles_Wedding_Party_Rentals.html" style="text-decoration: none">
|
||||
<font color="#000000">Wedding Party
|
||||
Rentals</font>
|
||||
</a></td>
|
||||
<td width="34%" align="center" height="38">Outdoor
|
||||
Events Party Rentals</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="33%" align="center" height="42">
|
||||
<p align="center">Tents & Canopies Rentals
|
||||
</td>
|
||||
<td width="33%" align="center" height="42"> </td>
|
||||
<td width="34%" align="center" height="42"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="33%" align="center" height="19"> </td>
|
||||
<td width="33%" align="center" height="19"> </td>
|
||||
<td width="34%" align="center" height="19"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</div>
|
||||
</td>
|
||||
<td width="108" height="146" background="Images/shade_right.jpg"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%" bgcolor="#DE0404">
|
||||
<div align="center">
|
||||
<center>
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="1002"
|
||||
id="AutoNumber14">
|
||||
<tr>
|
||||
<td width="110">
|
||||
<p align="right">
|
||||
<img border="0" src="Images/red_bar_left.jpg" width="24" height="32">
|
||||
</td>
|
||||
<td width="782" bgcolor="#FFFFFF">
|
||||
<p align="center">
|
||||
<font face="Arial" style="font-size: 9pt">
|
||||
<a href="index.html" style="text-decoration: none">
|
||||
<font color="#000000">HOME</font>
|
||||
</a>
|
||||
| <a href="aboutus.html" style="text-decoration: none">
|
||||
<font color="#000000">ABOUT US</font>
|
||||
</a> |
|
||||
<a href="product_catalog.html" style="text-decoration: none">
|
||||
<font color="#000000">PRODUCT CATALOG</font>
|
||||
</a> |
|
||||
<a href="services.html" style="text-decoration: none">
|
||||
<font color="#000000">SERVICES</font>
|
||||
</a> |
|
||||
<a href="gallery.html" style="text-decoration: none">
|
||||
<font color="#000000">EVENT GALLERY</font>
|
||||
</a> |
|
||||
<a href="servicerequest.html" style="text-decoration: none">
|
||||
<font color="#000000">SERVICE REQUEST</font>
|
||||
</a>
|
||||
| <a href="contactus.html" style="text-decoration: none">
|
||||
<font color="#000000">CONTACT US</font>
|
||||
</a></font>
|
||||
</td>
|
||||
<td width="110">
|
||||
<img border="0" src="Images/red_bar_right.jpg" width="24" height="32"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="100%">
|
||||
<p align="center">
|
||||
<font face="Arial" style="font-size: 7pt">Copyright ©
|
||||
2013 Hillcrest Party Rentals. All rights reserved. Designed by Kt
|
||||
Designing Inc.</font>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -103,6 +103,8 @@
|
|||
</head>
|
||||
<body id="cp" >
|
||||
|
||||
<h2> asdfsdf <h2>
|
||||
|
||||
<div class="page">
|
||||
<div class="header">
|
||||
<a id="logo" href="http://www.ssense.com"><img style="height: 20px; width:126px" class="main_logo" src="http://www.ssense.com/frontend/images/gui/ssense_header_logo.png" alt="Designer clothing, luxury shoes & fashion accessories online | SSENSE"></a>
|
||||
|
|
|
|||
82
test/html/nice.html
Normal file
82
test/html/nice.html
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<div id="sesCountBox" style="background-color: transparent;">
|
||||
<div class="pop-up" style="padding-bottom: 30px;border-radius: 10px;position: absolute;left: 5%;background-color: rgba(0, 0, 0, 0.85);color: black;text-align: center;width: 90%;padding-top: 15px;font-size: 17px;font-family: sans-serif;display: flex;align-items: center;justify-content: center;">
|
||||
<div>
|
||||
<div style="margin-top: 10px;font-size: 70px;font-weight: 900;color: #ffffff;">Go Pro Now!</div>
|
||||
<div style="color: #2a8cdd;font-size: 20px;">Unlock all the features</div>
|
||||
<div style="padding: 30px;text-align: center;border: 0px;">
|
||||
<div style="display: inline-block;">
|
||||
<div id="priceContainer" style="display: inline-block;float: left;">
|
||||
<div id="yearlyPrice" style="width: 450px;height: 350px;color: #333333;margin: 15px;margin-left: 0px;margin-right: 30px;text-align: center;font-size: 25px;cursor: pointer;transition: background 0.15s linear;border-radius: 10px;border: solid;border-color: #ffffff;border-width: 1px;box-shadow: 3px 3px 12px rgba(0,0,0,0.44);">
|
||||
<div style="position: relative;background: #2a8cdd;border-color: #ffffff;border-top-left-radius: 8px;border-top-right-radius: 8px;color: #ffffff;font-size: 37px;line-height: 50px;text-transform: uppercase;border-style: solid;border-width: 0px;border-bottom-width: 1px;top: 0px!important;">
|
||||
<text msgid="save25">Save 25%</text>
|
||||
</div>
|
||||
<div style="position: relative;margin: auto;content: "";width: 0;height: 0;border-left: 16px solid transparent;border-right: 16px solid rgba(255, 255, 255, 0);border-top: 16px solid #ffffff;top: 0px!important;"></div>
|
||||
<div style="position: relative;margin: auto;content: "";width: 0;height: 0;border-left: 16px solid transparent;border-right: 16px solid transparent;border-top: 16px solid #2a8cdd;z-index: 999;top: -18px!important;"></div>
|
||||
<div style="top: 5px;position: relative;">
|
||||
<text msgid="yearlyPrice_alone" style="color: #ffffff;font-weight: bold;font-size: 70px;line-height: 60px;text-transform: uppercase;padding-top: 20px;display: block;">$2.99</text>
|
||||
<br>
|
||||
<text msgid="monthly" style="font-size: 30px;color: #ffffff;margin-top: -20px;display: block;">Monthly</text>
|
||||
<text msgid="annual_billing" style="font-size: 15px;color: #ffffff;margin-top: 0px;display: block;">Annual Billing</text>
|
||||
</div>
|
||||
<div id="sesUpgradeButton" style="top: 0px;background-color: #49bd11;box-shadow: 0px 3px 0px #308404;position: relative;font-weight: 400;height: 45px;padding-top: 5px;padding-bottom: 5px;border-radius: 7px;text-align: center;font-size: 40px;text-transform: uppercase;color: white;cursor: pointer;transition: background 0.15s linear;padding-left: 0px;padding-right: 0px;width: 400px;margin: auto;margin-top: 30px;" >
|
||||
<text msgid="upgrade_now">Upgrade Now</text>
|
||||
</div>
|
||||
</div>
|
||||
<div id="checkoutStudentsTeachers1" style="display: none;color: #333333;width: 200px;height: 200px;margin: 15px;margin-left: 0px;margin-right: 30px;text-align: center;font-size: 25px;cursor: pointer;transition: background 0.15s linear;border-radius: 10px;border: solid;border-color: #ffffff;border-width: 1px;box-shadow: 3px 3px 12px rgba(0,0,0,0.44);">
|
||||
<div style="position: relative;top: 30%;">
|
||||
<text msgid="checkout_schools" style="font-size: 25px;">Offer for Schools</text>
|
||||
</div>
|
||||
<div id="priceButtonStudents" style="position: relative;top: 30%;">
|
||||
<text msgid="upgrade_now">Upgrade Now</text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="featuresContainer" style="text-align: left;float: left;display: inline-block;margin-left: 50px;margin-top: 30px;">
|
||||
<table id="featuresTable" style="margin-bottom: 10px;">
|
||||
<tbody>
|
||||
<tr style="font-size: 25px;color: #2a8cdd;">
|
||||
<td colspan="2" style="padding-bottom: 15px;">
|
||||
<text msgid="what_get_pro">Pro Version Features:</text>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="font-size: 18px;color: #ffffff;font-weight: 100;">
|
||||
<td style="padding-bottom: 15px;"><img src="<!--%img/icons/check-30.png%-->" style="padding-top: 6px;margin-right: 15px;"></td>
|
||||
<td style="padding-bottom: 15px;">
|
||||
<text msgid="unlimited_reading"><strong>Unlimited</strong> Words for non-stop Listening</text>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="font-size: 18px;color: #ffffff;font-weight: 100;">
|
||||
<td style="padding-bottom: 15px;"><img src="<!--%img/icons/check-30.png%-->" style="padding-top: 6px;margin-right: 15px;"></td>
|
||||
<td style="padding-bottom: 15px;">
|
||||
<text msgid="no_barriers">Read Text in 27 Different Languages</text>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="font-size: 18px;color: #ffffff;font-weight: 100;">
|
||||
<td style="padding-bottom: 15px;"><img src="<!--%img/icons/check-30.png%-->" style="padding-top: 6px;margin-right: 15px;"></td>
|
||||
<td style="padding-bottom: 15px;">
|
||||
<text msgid="fast_or_slow">Read Text at 21 Different Speed Settings</text>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="font-size: 18px;color: #ffffff;font-weight: 100;">
|
||||
<td style="padding-bottom: 15px;"><img src="<!--%img/icons/check-30.png%-->" style="padding-top: 6px;margin-right: 15px;"></td>
|
||||
<td style="padding-bottom: 15px;">
|
||||
<text msgid="read_autoplay">Autoplay Text - Fast and Easy</text>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="font-size: 18px;color: #ffffff;font-weight: 100;">
|
||||
<td style="padding-bottom: 15px;"><img src="<!--%img/icons/check-30.png%-->" style="padding-top: 6px;margin-right: 15px;"></td>
|
||||
<td style="padding-bottom: 15px;">
|
||||
<text msgid="detect_lang">Automatic Language Detection</text>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sesContinue">
|
||||
<div id="sesCountNumber" style="cursor: pointer;top: calc(50% - 0.5em);position: relative;font-size: 23px;text-align: center;color: #afafaf;text-decoration: underline;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Loading…
Reference in a new issue