added html validation

This commit is contained in:
taoqf 2019-01-21 11:53:28 +08:00
parent ba58c88fcd
commit ddf2b8e07e
13 changed files with 3236 additions and 165 deletions

18
.vscode/launch.json vendored Normal file
View 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"
]
}
]
}

View file

@ -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']);

View file

@ -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"
}
}

View file

@ -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
View 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())

View file

@ -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
View 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 &lt;input_here&gt; "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&amp;t=631&amp;hilit=mpeg4+libx264+cores&amp;start=10#p2163"><span
class="icon"></span>http://ffmpeg.zeranoe.com/forum/viewtopic.php?f=7&amp;t=631&amp;hilit=mpeg4+libx264+cores&amp;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&amp;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://&lt;wowza server
IP&gt;/live/cam0'
</p>
</blockquote>
<blockquote>
<p>
-i 'udp://localhost:5000?fifo_size=1000000&amp;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://&lt;wowza server IP&gt;/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&amp;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&amp;version=82" title="Version 82 by rogerdpack">Last modified</a>
<a class="timeline" href="/timeline?from=2018-08-30T01%3A49%3A59%2B03%3A00&amp;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 &quot;Helvetica Neue&quot;, Helvetica, Arial, Geneva, sans-serif; cursor: pointer; padding: 5px;"><span
style="color: white; font: 12px &quot;Helvetica Neue&quot;, 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

File diff suppressed because one or more lines are too long

9
test/html/google.html Normal file

File diff suppressed because one or more lines are too long

View 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 &amp; 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">&nbsp;</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">&nbsp;</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!&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</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&nbsp;&nbsp;&nbsp;&nbsp;
</font>
</td>
</tr>
<tr>
<td width="100%" height="22" colspan="5">
<p align="center">&nbsp;
</td>
</tr>
<tr>
<td width="29%" height="31">
<p align="center">&nbsp;
</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">&nbsp;</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">&nbsp;&nbsp; <br>
</span>&nbsp;&nbsp; </td>
</tr>
<tr>
<td width="110" height="19" background="Images/shade_left.jpg">&nbsp;</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">&nbsp;</td>
</tr>
<tr>
<td width="110" height="146" background="Images/shade_left.jpg">&nbsp;</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">&nbsp;</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">&nbsp;</td>
</tr>
<tr>
<td width="100%" colspan="4" height="19">Our PRODUCT
CATALOG<br>
&nbsp;</td>
</tr>
<tr>
<td width="5%" height="20">&nbsp;</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">&nbsp;</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">&nbsp;</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 &amp;
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">&nbsp;</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 &amp;
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">&nbsp;</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">&nbsp;</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">&nbsp;</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 &amp;
Serving</font>
</a></font>
</td>
</tr>
<tr>
<td width="5%" height="21">&nbsp;</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 &amp;
Garden</font>
</a></font>
</td>
</tr>
<tr>
<td width="5%" height="38">&nbsp;</td>
<td width="7%" height="38">&nbsp;</td>
<td width="44%" height="38">&nbsp;</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&amp;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">&nbsp;</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 &amp; 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">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<img border="0" src="Images/hp_photo2.jpg" width="129" height="180">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<img border="0" src="Images/hp_photo3.jpg" width="129" height="180">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<img border="0" src="Images/hp_photo4.jpg" width="129" height="180">
</td>
</tr>
<tr>
<td width="674" height="19" colspan="3">&nbsp;</td>
</tr>
<tr>
<td width="674" height="30" colspan="3" bgcolor="#AC0505">&nbsp;<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:&nbsp;&nbsp;
</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 &amp; Canopies Rentals
</td>
<td width="33%" align="center" height="42">&nbsp;</td>
<td width="34%" align="center" height="42">&nbsp;</td>
</tr>
<tr>
<td width="33%" align="center" height="19">&nbsp;</td>
<td width="33%" align="center" height="19">&nbsp;</td>
<td width="34%" align="center" height="19">&nbsp;</td>
</tr>
</table>
</center>
</div>
</td>
</tr>
</table>
</center>
</div>
</td>
<td width="108" height="146" background="Images/shade_right.jpg">&nbsp;</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>&nbsp;
|&nbsp; <a href="aboutus.html" style="text-decoration: none">
<font color="#000000">ABOUT US</font>
</a>&nbsp; |&nbsp;
<a href="product_catalog.html" style="text-decoration: none">
<font color="#000000">PRODUCT CATALOG</font>
</a>&nbsp; |&nbsp;
<a href="services.html" style="text-decoration: none">
<font color="#000000">SERVICES</font>
</a>&nbsp; |&nbsp;
<a href="gallery.html" style="text-decoration: none">
<font color="#000000">EVENT GALLERY</font>
</a>&nbsp; |&nbsp;
<a href="servicerequest.html" style="text-decoration: none">
<font color="#000000">SERVICE REQUEST</font>
</a>&nbsp;
|&nbsp; <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>

View file

@ -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
View 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: &quot;&quot;;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: &quot;&quot;;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>

1127
yarn.lock Normal file

File diff suppressed because it is too large Load diff