Skip to content

Commit 54417e0

Browse files
committed
Release as first version: v0.1.0
1 parent 9d029c6 commit 54417e0

File tree

4 files changed

+333
-2
lines changed

4 files changed

+333
-2
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ v8.log
1818

1919
# Ignore coverage results
2020
/coverage/
21+
22+
# Ignore test files
23+
scratch.js

README.md

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
11
# Epinfer
22

3-
Extract as much information as possible from a video filename.
3+
Extract as much information as possible from a video filename.
4+
Inspired by wackou's `guessit` python module, but in no way as feature complete.
5+
6+
# Installation
7+
8+
```bash
9+
npm install epinfer
10+
```
11+
12+
# Usage
13+
14+
```javascript
15+
var epinfer = require('epinfer');
16+
17+
// Call the process method with a filename as parameter
18+
epinfer.process('Homeland.S02E01.HDTV.x264-EVOLVE.mp4');
19+
20+
// An object will be returned
21+
/*
22+
{ season: 2,
23+
episode: 1,
24+
series: 'Homeland',
25+
format: 'HDTV',
26+
video_codec: 'h264' }
27+
*/
28+
```

lib/init.js

+300
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
var Blast = require('protoblast')(false),
2+
Bound = Blast.Bound,
3+
extensions;
4+
5+
/**
6+
* The Epinfer filename class
7+
*
8+
* @constructor
9+
*
10+
* @author Jelle De Loecker <[email protected]>
11+
* @since 0.1.0
12+
* @version 0.1.0
13+
*
14+
* @param {String} filename
15+
*/
16+
var Epinfer = Blast.Collection.Function.inherits('Informer', function Epinfer() {
17+
this.properties = {};
18+
});
19+
20+
/**
21+
* Register a property to extract
22+
*
23+
* @author Jelle De Loecker <[email protected]>
24+
* @since 0.1.0
25+
* @version 0.1.0
26+
*
27+
* @param {String} name The name of the property
28+
* @param {String} value The name of the value
29+
* @param {Array} checks Array of strings/regexps
30+
* @param {Array} hasProp Only check if this property is present
31+
*/
32+
Epinfer.setMethod(function registerProperty(name, value, checks, quality, hasProp) {
33+
34+
var hasPropValue,
35+
extract;
36+
37+
if (!this.properties[name]) {
38+
this.properties[name] = {};
39+
}
40+
41+
if (typeof value == 'number') {
42+
extract = value;
43+
value = 'extract-' + value;
44+
} else {
45+
extract = false;
46+
}
47+
48+
checks = Bound.Array.cast(checks);
49+
hasProp = Bound.Array.cast(hasProp);
50+
51+
hasPropValue = hasProp[1];
52+
hasProp = hasProp[0];
53+
54+
this.properties[name][value] = {
55+
checks: checks,
56+
quality: quality || 0,
57+
hasProp: hasProp,
58+
hasPropValue: hasPropValue,
59+
extract: extract,
60+
value: value
61+
};
62+
});
63+
64+
/**
65+
* Process the given filename
66+
*
67+
* @author Jelle De Loecker <[email protected]>
68+
* @since 0.1.0
69+
* @version 0.1.0
70+
*
71+
* @param {String} filename
72+
*/
73+
Epinfer.setMethod(function process(filename) {
74+
75+
var propname,
76+
len,
77+
i;
78+
79+
this.filename = filename;
80+
this.checked = {};
81+
this.result = {};
82+
this.delay = [];
83+
84+
for (propname in this.properties) {
85+
this.extractProperty(propname);
86+
}
87+
88+
len = this.delay.length;
89+
90+
// Do the delayed checks
91+
for (i = 0; i < len; i++) {
92+
this.extractProperty(this.delay[i]);
93+
}
94+
95+
if (this.result.series) {
96+
this.result.series = this.result.series.replace(/\./g, ' ').trim();
97+
}
98+
99+
return this.result;
100+
});
101+
102+
/**
103+
* Extract the wanted property of the filename in the object
104+
*
105+
* @author Jelle De Loecker <[email protected]>
106+
* @since 0.1.0
107+
* @version 0.1.0
108+
*
109+
* @param {String} name
110+
*/
111+
Epinfer.setMethod(function extractProperty(name) {
112+
113+
var propvalname,
114+
property,
115+
filename,
116+
propval,
117+
found,
118+
check,
119+
temp,
120+
t,
121+
i;
122+
123+
filename = this.filename;
124+
property = this.properties[name];
125+
found = false;
126+
127+
for (propvalname in property) {
128+
propval = property[propvalname];
129+
130+
// If this property requires another property, make sure it has run & is present
131+
if (propval.hasProp) {
132+
133+
// Required property has been checked
134+
if (this.checked[propval.hasProp] === true) {
135+
136+
// It has not been found, so check next value
137+
if (this.result[propval.hasProp] == null) {
138+
continue;
139+
}
140+
141+
// The expected value has not been found
142+
if (propval.hasPropValue && this.result[propval.hasProp] != propval.hasPropValue) {
143+
continue;
144+
}
145+
} else {
146+
// Defer check to later!
147+
this.delay.push(name);
148+
return;
149+
}
150+
}
151+
152+
for (i = 0; i < propval.checks.length; i++) {
153+
check = propval.checks[i];
154+
155+
if (typeof check == 'string') {
156+
temp = filename.indexOf(check);
157+
158+
if (temp > -1) {
159+
found = true;
160+
temp = propval.value;
161+
}
162+
} else {
163+
temp = check.exec(filename);
164+
165+
if (temp) {
166+
if (propval.extract === false) {
167+
found = true;
168+
temp = propval.value;
169+
} else {
170+
if (temp[propval.extract]) {
171+
found = true;
172+
temp = temp[propval.extract];
173+
}
174+
}
175+
}
176+
}
177+
178+
if (found) {
179+
t = Number(temp);
180+
181+
if (temp == t) {
182+
temp = t;
183+
}
184+
185+
this.result[name] = temp;
186+
break;
187+
}
188+
}
189+
190+
if (found) {
191+
break;
192+
}
193+
}
194+
195+
this.checked[name] = true;
196+
});
197+
198+
/**
199+
* Video extensions
200+
*
201+
* @type {Array}
202+
*/
203+
extensions = ['3g2', '3gp', '3gp2', 'asf', 'avi', 'divx', 'flv', 'm4v', 'mk2',
204+
'mka', 'mkv', 'mov', 'mp4', 'mp4a', 'mpeg', 'mpg', 'ogg', 'ogm',
205+
'ogv', 'qt', 'ra', 'ram', 'rm', 'ts', 'wav', 'webm', 'wma', 'wmv',
206+
'iso'];
207+
208+
Epinfer.setProperty('extensions', extensions);
209+
210+
var instance = new Epinfer();
211+
212+
// Get the season
213+
instance.registerProperty('season', 2, [
214+
/(.*)\D(\d{1,2})[ex\-](\d{1,2})/i,
215+
/(.*)Season.*?(\d{1,2}).*Episode\D*?(\d{1,2})/i,
216+
/(.*)\D(\d)(\d\d)\D/]);
217+
218+
// Get the episode
219+
instance.registerProperty('episode', 3, [
220+
/(.*)\D(\d{1,2})[ex\-](\d{1,2})/i,
221+
/(.*)Season.*?(\d{1,2}).*Episode\D*?(\d{1,2})/i,
222+
/(.*)\D(\d)(\d\d)\D/]);
223+
224+
// Get the series name
225+
instance.registerProperty('series', 1, [
226+
/(.*)\D(\d{1,2})[ex\-](\d{1,2})/i,
227+
/(.*)Season.*?(\d{1,2}).*Episode\D*?(\d{1,2})/i,
228+
/(.*)\D(\d)(\d\d)\D/]);
229+
230+
// Register all release formats
231+
instance.registerProperty('format', 'VHS', ['VHS', 'VHS-Rip'], -100);
232+
instance.registerProperty('format', 'Cam', ['CAM', 'CAMRip', 'HD-CAM'], -90);
233+
instance.registerProperty('format', 'Telesync', ['TS', 'HD-TS', 'TELESYNC', 'PDVD'], -80);
234+
instance.registerProperty('format', 'Workprint', ['WORKPRINT', 'WP'], -70);
235+
instance.registerProperty('format', 'Telecine', ['TELECINE', 'TC'], -60);
236+
instance.registerProperty('format', 'PPV', ['PPV', 'PPV-Rip'], -50);
237+
instance.registerProperty('format', 'TV', ['SD-TV', 'SD-TV-Rip', 'Rip-SD-TV', 'TV-Rip', 'Rip-TV'], -30);
238+
instance.registerProperty('format', 'DVB', ['DVB-Rip', 'DVB', 'PD-TV'], -20);
239+
instance.registerProperty('format', 'DVD', ['DVD', 'DVD-Rip', 'VIDEO-TS', 'DVD-R', 'DVD-9', 'DVD-5'], 0);
240+
instance.registerProperty('format', 'HDTV', ['HDTV', 'HD-TV', 'TV-RIP-HD', 'HD-TV-RIP'], 20);
241+
instance.registerProperty('format', 'VOD', ['VOD', 'VOD-Rip'], 40);
242+
instance.registerProperty('format', 'WEBRip', ['WEB-Rip'], 50);
243+
instance.registerProperty('format', 'WEB-DL', ['WEB-DL', 'WEB-HD', 'WEB'], 60);
244+
instance.registerProperty('format', 'HD-DVD', ['HD-(?:DVD)?-Rip', 'HD-DVD'], 80);
245+
instance.registerProperty('format', 'BluRay', ['Blu-ray(?:-Rip)?', 'B[DR]', 'B[DR]-Rip', 'BD[59]', 'BD25', 'BD50'], 100);
246+
247+
// Register all resolutions
248+
instance.registerProperty('screen_size', '360p', [/(?:\d{3,}(?:\\|\/|x|\*))?360(?:i|p?x?)/i], -300);
249+
instance.registerProperty('screen_size', '368p', [/(?:\d{3,}(?:\\|\/|x|\*))?368(?:i|p?x?)/i], -200);
250+
instance.registerProperty('screen_size', '480p', [/(?:\d{3,}(?:\\|\/|x|\*))?480(?:i|p?x?)/i], -100);
251+
instance.registerProperty('screen_size', '576p', [/(?:\d{3,}(?:\\|\/|x|\*))?576(?:i|p?x?)/i], 0);
252+
instance.registerProperty('screen_size', '720p', [/(?:\d{3,}(?:\\|\/|x|\*))?720(?:i|p?x?)/i], 100);
253+
instance.registerProperty('screen_size', '900p', [/(?:\d{3,}(?:\\|\/|x|\*))?900(?:i|p?x?)/i], 130);
254+
instance.registerProperty('screen_size', '1080i', [/(?:\d{3,}(?:\\|\/|x|\*))?1080i/i], 180);
255+
instance.registerProperty('screen_size', '1080p', [/(?:\d{3,}(?:\\|\/|x|\*))?1080p?x?/i], 200);
256+
instance.registerProperty('screen_size', '4K', [/(?:\d{3,}(?:\\|\/|x|\*))?2160(?:i|p?x?)/i], 400);
257+
258+
// Register video codecs
259+
instance.registerProperty('video_codec', 'Real', [/Rv\d{2}/i], -50);
260+
instance.registerProperty('video_codec', 'Mpeg2', ['Mpeg2'], -30);
261+
instance.registerProperty('video_codec', 'DivX', ['DVDivX', 'DivX'], -10);
262+
instance.registerProperty('video_codec', 'XviD', ['XviD'], 0);
263+
instance.registerProperty('video_codec', 'h264', [/[hx]-?264(?:-AVC)?/i, /MPEG-?4(?:-AVC)/i], 100);
264+
instance.registerProperty('video_codec', 'h265', [/[hx]-?265(?:-HEVC)?/i, 'HEVC'], 150);
265+
266+
// Register video profiles
267+
instance.registerProperty('video_profile', 'BP', 'BP', -20, 'video_codec');
268+
instance.registerProperty('video_profile', 'XP', ['XP', 'EP'], -10, 'video_codec');
269+
instance.registerProperty('video_profile', 'MP', 'MP', 0, 'video_codec');
270+
instance.registerProperty('video_profile', 'HP', ['HP', 'HiP'], 10, 'video_codec');
271+
instance.registerProperty('video_profile', '10bit', [/10.?bit/, 'Hi10P'], 15);
272+
instance.registerProperty('video_profile', '8bit', [/8.?bit/], 15);
273+
instance.registerProperty('video_profile', 'Hi422P', 'Hi422P', 25, 'video_codec');
274+
instance.registerProperty('video_profile', 'Hi444P', 'Hi444PP', 25, 'video_codec');
275+
276+
instance.registerProperty('video_api', 'DXVA', 'DXVA');
277+
278+
// Register audio codecs
279+
instance.registerProperty('audio_codec', 'MP3', ['MP3', 'LAME', /LAME(?:\d)+-(?:\d)+/], 10);
280+
instance.registerProperty('audio_codec', 'DolbyDigital', ['DD'], 30);
281+
instance.registerProperty('audio_codec', 'AAC', ['AAC'], 35);
282+
instance.registerProperty('audio_codec', 'AC3', ['AC3'], 40);
283+
instance.registerProperty('audio_codec', 'Flac', ['FLAC'], 45);
284+
instance.registerProperty('audio_codec', 'DTS', ['DTS'], 60);
285+
instance.registerProperty('audio_codec', 'TrueHD', ['True-HD'], 70);
286+
287+
// Register audio profiles
288+
instance.registerProperty('audio_profile', 'HD', 'HD', 20, ['audio_codec', 'DTS']);
289+
instance.registerProperty('audio_profile', 'HD-MA', 'HDMA', 50, ['audio_codec', 'DTS']);
290+
instance.registerProperty('audio_profile', 'HE', 'HE', 20, ['audio_codec', 'AAC']);
291+
instance.registerProperty('audio_profile', 'LC', 'LC', 0, ['audio_codec', 'AAC']);
292+
instance.registerProperty('audio_profile', 'HQ', 'HQ', 0, ['audio_codec', 'AC3']);
293+
294+
// Register audio channels
295+
instance.registerProperty('audio_channels', '7.1', [/7[\W_]1/, '7ch', '8ch'], 200);
296+
instance.registerProperty('audio_channels', '5.1', [/5[\W_]1/, '5ch', '6ch'], 100);
297+
instance.registerProperty('audio_channels', '2.0', [/2[\W_]0/, '2ch', 'stereo'], 0);
298+
instance.registerProperty('audio_channels', '1.0', [/1[\W_]0/, '1ch', 'mono'], -100);
299+
300+
module.exports = instance;

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
{
22
"name": "epinfer",
33
"description": "Extract as much information as possible from a video filename.",
4-
"version": "0.1.0-alpha",
4+
"version": "0.1.0",
55
"author": "Jelle De Loecker <[email protected]>",
66
"keywords": ["episode", "video", "filename"],
77
"main": "./lib/init.js",
88
"repository": "[email protected]:skerit/epinfer.git",
99
"license": "MIT",
10+
"dependencies": {
11+
"protoblast": "0.1.x"
12+
},
1013
"engines": {
1114
"node": ">=0.10"
1215
}

0 commit comments

Comments
 (0)