@pixwel/pixwel-sdk 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +3 -0
- package/.editorconfig +36 -0
- package/.vs/slnx.sqlite +0 -0
- package/.vscode/launch.json +0 -0
- package/coverage/clover.xml +7 -0
- package/coverage/coverage-final.json +1 -0
- package/coverage/lcov-report/Ass.js.html +651 -0
- package/coverage/lcov-report/Asset.js.html +129 -0
- package/coverage/lcov-report/Filename.js.html +849 -0
- package/coverage/lcov-report/Ingest.js.html +591 -0
- package/coverage/lcov-report/IngestFile.js.html +87 -0
- package/coverage/lcov-report/IngestFileDrop.js.html +540 -0
- package/coverage/lcov-report/Order.js.html +162 -0
- package/coverage/lcov-report/Srt.js.html +333 -0
- package/coverage/lcov-report/Sub.js.html +177 -0
- package/coverage/lcov-report/TimeFormat.js.html +210 -0
- package/coverage/lcov-report/base.css +223 -0
- package/coverage/lcov-report/block-navigation.js +63 -0
- package/coverage/lcov-report/index.html +84 -0
- package/coverage/lcov-report/lib/Filename.js.html +720 -0
- package/coverage/lcov-report/lib/index.html +97 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +1 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +158 -0
- package/coverage/lcov-report/test/index.html +97 -0
- package/coverage/lcov-report/test/mocks.js.html +423 -0
- package/coverage/lcov.info +0 -0
- package/dist/index.js +61 -0
- package/dist/src/Ass.js +200 -0
- package/dist/src/Asset.js +41 -0
- package/dist/src/Filename.js +252 -0
- package/dist/src/Ingest.js +200 -0
- package/dist/src/IngestFile.js +17 -0
- package/dist/src/IngestFileDrop.js +178 -0
- package/dist/src/Order.js +58 -0
- package/dist/src/Srt.js +102 -0
- package/dist/src/Sub.js +42 -0
- package/dist/src/TimeFormat.js +48 -0
- package/index.js +6 -0
- package/package.json +26 -0
- package/src/Ass.js +195 -0
- package/src/Asset.js +20 -0
- package/src/Filename.js +260 -0
- package/src/Ingest.js +175 -0
- package/src/IngestFile.js +6 -0
- package/src/IngestFileDrop.js +157 -0
- package/src/Order.js +31 -0
- package/src/Srt.js +89 -0
- package/src/Sub.js +37 -0
- package/src/TimeFormat.js +48 -0
- package/src/docs.json +1 -0
- package/test/Ass.spec.js +150 -0
- package/test/Asset.spec.js +24 -0
- package/test/Filename.spec.js +728 -0
- package/test/Ingest.spec.js +95 -0
- package/test/Srt.spec.js +111 -0
- package/test/Sub.spec.js +75 -0
- package/test/TimeFormat.spec.js +84 -0
- package/test/mocks/mediainfo.mock.js +67 -0
- package/test/mocks/mediainfo.png.mock.js +22 -0
- package/test/mocks/mocks.js +118 -0
- package/test/mocks/prores.mock.js +71 -0
- package/test/parse.spec.ts +927 -0
- package/tsconfig.json +11 -0
package/src/Filename.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Filename class parses a filename into tokens, and can also take a set of
|
|
5
|
+
* tokens and create a valid Pixwel filename.
|
|
6
|
+
* @type class
|
|
7
|
+
*/
|
|
8
|
+
export default class Filename {
|
|
9
|
+
/**
|
|
10
|
+
* Renders a valid filename given all tags, all types, an asset, and metadata
|
|
11
|
+
* options likely parsed from the file itself.
|
|
12
|
+
* @param {Array} [allTags=[]] Collection of all known tags
|
|
13
|
+
* @param {Array} [allTypes=[]] Collection of all known types
|
|
14
|
+
* @param {Object} [asset={}] A Platform Asset
|
|
15
|
+
* @param {Object} [options={}] options containing metadata about file
|
|
16
|
+
* @param {String} [mediaType=''] Indicates video, audio, image and allows for stricter required fields
|
|
17
|
+
* @return {String} A valid filename
|
|
18
|
+
*/
|
|
19
|
+
static render(
|
|
20
|
+
allTags = [],
|
|
21
|
+
allTypes = [],
|
|
22
|
+
asset = {},
|
|
23
|
+
options = {},
|
|
24
|
+
mediaType = ""
|
|
25
|
+
) {
|
|
26
|
+
if (!asset.project) {
|
|
27
|
+
throw "missing asset.project";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (asset.project && !asset.project.filePrefix) {
|
|
31
|
+
throw "missing asset.project.filePrefix";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!asset.name) {
|
|
35
|
+
throw "missing asset.name";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!asset.type) {
|
|
39
|
+
throw "missing asset.type";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (asset.type.match(/^film-clip|^featurette/) && !asset.length) {
|
|
43
|
+
throw "missing asset.length";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let defaults = {
|
|
47
|
+
language: null,
|
|
48
|
+
country: null,
|
|
49
|
+
version: null,
|
|
50
|
+
tags: {
|
|
51
|
+
Usage: null,
|
|
52
|
+
Aspect: null,
|
|
53
|
+
SubDub: {
|
|
54
|
+
sub: null,
|
|
55
|
+
dub: null
|
|
56
|
+
},
|
|
57
|
+
Resolution: null,
|
|
58
|
+
"Frame Mode": null,
|
|
59
|
+
Framerate: null,
|
|
60
|
+
Text: null,
|
|
61
|
+
Codec: null,
|
|
62
|
+
Extra: []
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
options = _.merge(defaults, options);
|
|
66
|
+
|
|
67
|
+
if (!options.language) {
|
|
68
|
+
throw "missing options.language";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (
|
|
72
|
+
!options.tags.SubDub.sub &&
|
|
73
|
+
!options.tags.SubDub.dub &&
|
|
74
|
+
!options.tags.Text
|
|
75
|
+
) {
|
|
76
|
+
throw "missing options.tags.Text or options.tags.SubDub";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (mediaType && mediaType == "video" && !options.tags.Codec) {
|
|
80
|
+
throw "missing options.tags.Codec";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (mediaType && mediaType == "video" && !options.tags.Resolution) {
|
|
84
|
+
throw "missing options.tags.Resolution";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (mediaType && mediaType == "video" && !options.tags.Framerate) {
|
|
88
|
+
throw "missing options.tags.Framerate";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (mediaType == "video" && !options.tags["Frame Mode"]) {
|
|
92
|
+
throw "missing options.tags[Frame Mode]";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// currently, slurpee's filename.ts will render this, but we can add this back in later when
|
|
96
|
+
// filename.ts starts to rely on Filename.render
|
|
97
|
+
// if ((options.tags.SubDub.dub || options.tags.SubDub.sub) && options.tags.Text) {
|
|
98
|
+
// throw 'conflicting options supplied: SubDub and Text';
|
|
99
|
+
// }
|
|
100
|
+
|
|
101
|
+
function assetType(asset, types) {
|
|
102
|
+
if (/trailer/.test(asset.type)) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let matchingType = _.find(types, { name: asset.type });
|
|
107
|
+
if (!matchingType) {
|
|
108
|
+
throw `Asset type "${asset.type}" could not be found`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
matchingType.file || matchingType.name.replace(".", "-").toUpperCase()
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function assetTitle(asset) {
|
|
117
|
+
let prefix = "";
|
|
118
|
+
let name = asset.slug.toUpperCase().replace("INT-L", "INTL");
|
|
119
|
+
if (/trailer/.test(asset.type)) {
|
|
120
|
+
switch (asset.type) {
|
|
121
|
+
case "trailer.intl":
|
|
122
|
+
prefix = "ITR-";
|
|
123
|
+
name = name.replace(/INTL-TRAILER-/, "");
|
|
124
|
+
break;
|
|
125
|
+
case "trailer":
|
|
126
|
+
prefix = "TR-";
|
|
127
|
+
name = name.replace(/TRAILER-/, "");
|
|
128
|
+
break;
|
|
129
|
+
case "trailer.us":
|
|
130
|
+
prefix = "DTR-";
|
|
131
|
+
name = name.replace(/DOMESTIC-TRAILER-/, "");
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return prefix + name;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function runningTime(asset) {
|
|
139
|
+
let matches = asset.type.match(/^film-clip|^featurette/);
|
|
140
|
+
if (!matches) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let minutes = Math.floor(asset.length / 60);
|
|
145
|
+
let seconds = asset.length % 60;
|
|
146
|
+
|
|
147
|
+
let result = "";
|
|
148
|
+
|
|
149
|
+
if (minutes > 0) {
|
|
150
|
+
result += minutes + "min";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (seconds > 0) {
|
|
154
|
+
result += seconds + "sec";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function locale(country, language) {
|
|
161
|
+
return [language, country]
|
|
162
|
+
.filter(str => {
|
|
163
|
+
return !!str && str !== "WW";
|
|
164
|
+
})
|
|
165
|
+
.join("-");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function textType(subDub, text) {
|
|
169
|
+
if (!subDub || (!subDub.sub && !subDub.dub)) {
|
|
170
|
+
return text;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let result = [];
|
|
174
|
+
if (subDub.sub) {
|
|
175
|
+
result.push("sub");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (subDub.dub) {
|
|
179
|
+
result.push("dub");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function matchSet(allTags, tags) {
|
|
186
|
+
let selectedTags = _(tags)
|
|
187
|
+
.values()
|
|
188
|
+
.flatten()
|
|
189
|
+
.value();
|
|
190
|
+
|
|
191
|
+
let setTag = _(allTags)
|
|
192
|
+
.filter("set")
|
|
193
|
+
.find(setTag => {
|
|
194
|
+
return _.difference(setTag.set, selectedTags).length === 0;
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (setTag) {
|
|
198
|
+
return setTag.name;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let tags = options.tags,
|
|
203
|
+
parts = [
|
|
204
|
+
asset.project.filePrefix,
|
|
205
|
+
assetType(asset, allTypes),
|
|
206
|
+
assetTitle(asset),
|
|
207
|
+
runningTime(asset),
|
|
208
|
+
locale(options.country, options.language),
|
|
209
|
+
textType(tags.SubDub, tags.Text),
|
|
210
|
+
tags.Extra.join("_"),
|
|
211
|
+
options.version,
|
|
212
|
+
tags.Codec
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
parts = _(parts)
|
|
216
|
+
.flatten()
|
|
217
|
+
.filter()
|
|
218
|
+
.map(part => part.toUpperCase());
|
|
219
|
+
|
|
220
|
+
let matchedSet = matchSet(allTags, options.tags);
|
|
221
|
+
let requiredInfo = tags.Resolution && tags["Frame Mode"] && tags.Framerate;
|
|
222
|
+
|
|
223
|
+
if (matchedSet) {
|
|
224
|
+
parts = parts.push(matchedSet);
|
|
225
|
+
} else if (mediaType == "video" && tags.Resolution && !requiredInfo) {
|
|
226
|
+
parts = parts.push(tags.Resolution.replace(/\d+x/, ""));
|
|
227
|
+
} else if (mediaType == "image" && tags.Resolution) {
|
|
228
|
+
parts = parts.push(tags.Resolution);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let name = parts.join("_");
|
|
232
|
+
|
|
233
|
+
let hasResInfo = name.match(/\d+(i|p)/i);
|
|
234
|
+
let hasAllInfo = name.match(/\d+(i|p)\d+/i);
|
|
235
|
+
|
|
236
|
+
if (requiredInfo && !hasResInfo && !hasAllInfo) {
|
|
237
|
+
let res = tags.Resolution;
|
|
238
|
+
let encodeTag = [
|
|
239
|
+
hasResInfo ? "" : res.indexOf("x") ? res.replace(/\d+x/, "") : res,
|
|
240
|
+
res.match(/i|p/i) && !hasResInfo
|
|
241
|
+
? ""
|
|
242
|
+
: tags["Frame Mode"].toLowerCase().slice(0, 1),
|
|
243
|
+
tags.Framerate.replace(".", "").toUpperCase()
|
|
244
|
+
]
|
|
245
|
+
.filter(val => {
|
|
246
|
+
return val;
|
|
247
|
+
})
|
|
248
|
+
.join("");
|
|
249
|
+
|
|
250
|
+
let match = new RegExp(encodeTag, "i");
|
|
251
|
+
name = name.match(match)
|
|
252
|
+
? name.replace(match, encodeTag)
|
|
253
|
+
: name + "_" + encodeTag;
|
|
254
|
+
} else if (hasResInfo && !hasAllInfo && tags.Framerate) {
|
|
255
|
+
name += tags.Framerate.replace(".", "").toUpperCase();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return name;
|
|
259
|
+
}
|
|
260
|
+
}
|
package/src/Ingest.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* [media description]
|
|
5
|
+
* @type {[type]}
|
|
6
|
+
*/
|
|
7
|
+
export default class Ingest {
|
|
8
|
+
/**
|
|
9
|
+
* [mapMetaToTags description]
|
|
10
|
+
* @param {[type]} info [description]
|
|
11
|
+
* @return {[type]} [description]
|
|
12
|
+
*/
|
|
13
|
+
static mapMetaToTags(info, mediaType) {
|
|
14
|
+
|
|
15
|
+
let tags = {
|
|
16
|
+
Aspect: "",
|
|
17
|
+
Standard: "",
|
|
18
|
+
Resolution: "",
|
|
19
|
+
Codec: "",
|
|
20
|
+
"Frame Mode": "",
|
|
21
|
+
Text: "",
|
|
22
|
+
Usage: "",
|
|
23
|
+
Extra: [],
|
|
24
|
+
Framerate: "",
|
|
25
|
+
Duration: ""
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
switch (mediaType) {
|
|
29
|
+
case 'video':
|
|
30
|
+
mediaType = 'Video';
|
|
31
|
+
break;
|
|
32
|
+
case 'image':
|
|
33
|
+
mediaType = 'Image';
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
return tags;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!info.File) {
|
|
40
|
+
throw "missing info.File";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!info.File.track) {
|
|
44
|
+
throw "missing info.File.track";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let mediainfoDefaults = {
|
|
48
|
+
Display_aspect_ratio: "",
|
|
49
|
+
Format: "",
|
|
50
|
+
Standard: "",
|
|
51
|
+
Height: "",
|
|
52
|
+
Width: "",
|
|
53
|
+
Frame_rate: "",
|
|
54
|
+
Scan_type: ""
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
let media = _.find(info["File"]["track"], {
|
|
58
|
+
_type: mediaType
|
|
59
|
+
});
|
|
60
|
+
media = _.merge({}, mediainfoDefaults, media);
|
|
61
|
+
|
|
62
|
+
let width = parseInt(media["Width"].replace(" ", "").match(/[0-9 ]+/));
|
|
63
|
+
let height = parseInt(media["Height"].replace(" ", "").match(/[0-9 ]+/));
|
|
64
|
+
tags.Resolution = width && height ? `${width}x${height}` : "";
|
|
65
|
+
|
|
66
|
+
tags.Standard = media["Standard"] ? media["Standard"].toLowerCase() : "";
|
|
67
|
+
|
|
68
|
+
let scan = /Progressive/.test(media["Scan_type"]) ? "p" : "i";
|
|
69
|
+
let dimension = /3D/.test(name) ? "3D" : "2D";
|
|
70
|
+
|
|
71
|
+
let framerate = media["Frame_rate"].split(" ");
|
|
72
|
+
if (framerate.length) {
|
|
73
|
+
let f = framerate[0];
|
|
74
|
+
if (f == "23.976") {
|
|
75
|
+
f = "23.98";
|
|
76
|
+
}
|
|
77
|
+
f = parseFloat(f).toString();
|
|
78
|
+
tags.Framerate = f != "NaN" ? f : "";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!tags.Standard) {
|
|
82
|
+
switch (tags.Framerate) {
|
|
83
|
+
case "24":
|
|
84
|
+
case "23.98":
|
|
85
|
+
case "29.97":
|
|
86
|
+
case "59.94":
|
|
87
|
+
tags.Standard = "ntsc";
|
|
88
|
+
break;
|
|
89
|
+
case "25":
|
|
90
|
+
case "50":
|
|
91
|
+
tags.Standard = "pal";
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
tags.Codec = (function(codec) {
|
|
97
|
+
switch (codec) {
|
|
98
|
+
case "AVC":
|
|
99
|
+
return "h264";
|
|
100
|
+
case "ProRes":
|
|
101
|
+
return "prores";
|
|
102
|
+
default:
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
})(media["Format"]);
|
|
106
|
+
|
|
107
|
+
if (tags.Codec == "h264") {
|
|
108
|
+
tags.Usage = "online";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (tags.Codec == "prores") {
|
|
112
|
+
tags.Usage = "broadcast";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
tags.Aspect = media["Display_aspect_ratio"] || "";
|
|
116
|
+
tags["Frame Mode"] = media["Scan_type"].toLowerCase();
|
|
117
|
+
|
|
118
|
+
return tags;
|
|
119
|
+
}
|
|
120
|
+
static mapFromKnownData(context, order, tags, watermarks = []) {
|
|
121
|
+
|
|
122
|
+
if (order.asset) {
|
|
123
|
+
context = {
|
|
124
|
+
asset: order.asset._id,
|
|
125
|
+
project: order.project._id,
|
|
126
|
+
assetType: order.assetType,
|
|
127
|
+
purpose: "download"
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let previewType = (function() {
|
|
132
|
+
if (order.isSub() && order.isDub()) return "both";
|
|
133
|
+
if (order.isDub()) return "dub";
|
|
134
|
+
if (order.isSub()) return "sub";
|
|
135
|
+
})();
|
|
136
|
+
|
|
137
|
+
let isPreviewEnabled =
|
|
138
|
+
tags.Codec == "prores" &&
|
|
139
|
+
tags.Resolution.split("x")[1] == "1080" &&
|
|
140
|
+
(tags.Framerate == "23.98" || tags.Framerate == "25") &&
|
|
141
|
+
watermarks.length;
|
|
142
|
+
|
|
143
|
+
let watermark = watermarks[0] ? watermarks[0]._id : "";
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
workRequest: order._id,
|
|
147
|
+
selected: {
|
|
148
|
+
download: {
|
|
149
|
+
enabled: true,
|
|
150
|
+
country: order.country,
|
|
151
|
+
language: order.language,
|
|
152
|
+
tags: tags,
|
|
153
|
+
embargo: {
|
|
154
|
+
enabled: false
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
preview: {
|
|
158
|
+
enabled: isPreviewEnabled,
|
|
159
|
+
language: order.language,
|
|
160
|
+
revision: "Final",
|
|
161
|
+
type: previewType,
|
|
162
|
+
watermark: watermark,
|
|
163
|
+
embargo: {
|
|
164
|
+
enabled: false
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
thumbnail: {
|
|
168
|
+
enabled: false
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
context: context
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Asset, Filename, Order, Ingest, IngestFile } from "@pixwel/pixwel-sdk";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Collects file handles from HTML5, full paths from Aspera, inspects
|
|
5
|
+
* the dropped file using mediainfo, then renames the file according to
|
|
6
|
+
* the data from Mediainfo and the Order and submits it for upload.
|
|
7
|
+
* @type {Object}
|
|
8
|
+
*/
|
|
9
|
+
export default class IngestFileDrop {
|
|
10
|
+
/**
|
|
11
|
+
* Map that contains data from HTML5 file drop and Aspera file drop
|
|
12
|
+
* @type {Object}
|
|
13
|
+
*/
|
|
14
|
+
constructor(scope, Mediainfo, IngestUpload) {
|
|
15
|
+
this.map = {};
|
|
16
|
+
this.scope = scope;
|
|
17
|
+
this.IngestUpload = IngestUpload;
|
|
18
|
+
this.Mediainfo = Mediainfo;
|
|
19
|
+
this.asperaDropHandler = this.asperaDropHandler.bind(this);
|
|
20
|
+
this.htmlDropHandler = this.htmlDropHandler.bind(this);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* HTML file drop handler
|
|
24
|
+
* @param {[type]} e [description]
|
|
25
|
+
* @return {[type]} [description]
|
|
26
|
+
*/
|
|
27
|
+
htmlDropHandler(e) {
|
|
28
|
+
let files = e.dataTransfer.files;
|
|
29
|
+
for (var i = 0; i < files.length; i++) {
|
|
30
|
+
this.registerHtmlFile(files[i]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Aspera drop handler
|
|
35
|
+
* @param {[type]} data [description]
|
|
36
|
+
* @return {[type]} [description]
|
|
37
|
+
*/
|
|
38
|
+
asperaDropHandler(data) {
|
|
39
|
+
var that = this;
|
|
40
|
+
let files = data.files.dataTransfer.files;
|
|
41
|
+
if (!files.length) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (var i = 0; i < files.length; i++) {
|
|
45
|
+
this.registerAsperaFile(files[i]);
|
|
46
|
+
}
|
|
47
|
+
return Promise.all(this.export()).then(renamed => {
|
|
48
|
+
that.IngestUpload.uploadFiles(renamed, {}).then(() => {
|
|
49
|
+
that.map = {};
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Registers and HTML file drop
|
|
55
|
+
* @param {[type]} file [description]
|
|
56
|
+
* @return {[type]} [description]
|
|
57
|
+
*/
|
|
58
|
+
registerHtmlFile(file) {
|
|
59
|
+
this.map[file.name] = file;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Registers and Aspera file drop
|
|
63
|
+
* @param {[type]} file [description]
|
|
64
|
+
* @return {[type]} [description]
|
|
65
|
+
*/
|
|
66
|
+
registerAsperaFile(file) {
|
|
67
|
+
let basename = this.getBasename(file);
|
|
68
|
+
this.map[basename]["path"] = file.name;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Gets correct basename for MacOS and Windows
|
|
72
|
+
* @param {[type]} file [description]
|
|
73
|
+
* @return {[type]} [description]
|
|
74
|
+
*/
|
|
75
|
+
getBasename(file) {
|
|
76
|
+
return file.name[0] === "/"
|
|
77
|
+
? file.name.split("/").pop()
|
|
78
|
+
: file.name.split("\\").pop();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Renames and generates ingest record for internal filemap
|
|
82
|
+
* @return {[type]} [description]
|
|
83
|
+
*/
|
|
84
|
+
export() {
|
|
85
|
+
var that = this;
|
|
86
|
+
return Object.values(this.map).map(file => {
|
|
87
|
+
return that.rename(file);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Renames the file and decorates it with ingest data using data from
|
|
92
|
+
* mediainfo and the Order
|
|
93
|
+
* @param {[type]} file [description]
|
|
94
|
+
* @return {[type]} [description]
|
|
95
|
+
*/
|
|
96
|
+
rename(file) {
|
|
97
|
+
var that = this;
|
|
98
|
+
return this.Mediainfo.inform(file).then(meta => {
|
|
99
|
+
let order;
|
|
100
|
+
let asset;
|
|
101
|
+
let mediaType = file.type.split("/")[0];
|
|
102
|
+
|
|
103
|
+
let tags = Ingest.mapMetaToTags(meta, mediaType);
|
|
104
|
+
|
|
105
|
+
if (!that.scope.workRequest) {
|
|
106
|
+
order = new Order();
|
|
107
|
+
asset = new Asset();
|
|
108
|
+
tags.Text = "txtd";
|
|
109
|
+
file.ingest = Ingest.mapFromKnownData(
|
|
110
|
+
that.scope.context,
|
|
111
|
+
order,
|
|
112
|
+
tags,
|
|
113
|
+
that.scope.watermarks
|
|
114
|
+
);
|
|
115
|
+
return file;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
order = new Order(that.scope.workRequest);
|
|
119
|
+
asset = Asset.mapFromOrder(order);
|
|
120
|
+
|
|
121
|
+
let options = {
|
|
122
|
+
tags: tags,
|
|
123
|
+
country: order.country,
|
|
124
|
+
language: order.language
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (that.scope.request && that.scope.request.tag) {
|
|
128
|
+
options.tags.Extra = [that.scope.request.tag.replace(" ", "-")];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
options.tags.SubDub = {
|
|
132
|
+
sub: order.isSub(),
|
|
133
|
+
dub: order.isDub()
|
|
134
|
+
};
|
|
135
|
+
if (!order.isSub() && !order.isDub()) {
|
|
136
|
+
options.tags.Text = "txtd";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let basename = Filename.render(
|
|
140
|
+
that.scope.tags,
|
|
141
|
+
that.scope.assetTypes,
|
|
142
|
+
asset,
|
|
143
|
+
options,
|
|
144
|
+
mediaType
|
|
145
|
+
);
|
|
146
|
+
let extension = file.name.split(".").pop();
|
|
147
|
+
file.renderedName = `${basename}.${extension}`;
|
|
148
|
+
file.ingest = Ingest.mapFromKnownData(
|
|
149
|
+
that.scope.context,
|
|
150
|
+
order,
|
|
151
|
+
tags,
|
|
152
|
+
that.scope.watermarks
|
|
153
|
+
);
|
|
154
|
+
return file;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
package/src/Order.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
import Filename from "./Filename.js";
|
|
3
|
+
|
|
4
|
+
export default class Order {
|
|
5
|
+
constructor(data) {
|
|
6
|
+
let schema = {
|
|
7
|
+
_id: null,
|
|
8
|
+
asset: null,
|
|
9
|
+
project: null,
|
|
10
|
+
assetType: null,
|
|
11
|
+
country: null,
|
|
12
|
+
language: null,
|
|
13
|
+
dng: null
|
|
14
|
+
};
|
|
15
|
+
Object.assign(this, schema, data);
|
|
16
|
+
}
|
|
17
|
+
isDub() {
|
|
18
|
+
if (!this.dng) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return _
|
|
22
|
+
.flattenDeep(Object.entries(this.dng))
|
|
23
|
+
.includes("Dedicated / Localized");
|
|
24
|
+
}
|
|
25
|
+
isSub() {
|
|
26
|
+
if (!this.dng) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return _.flattenDeep(Object.entries(this.dng)).includes("Subtitled");
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/Srt.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import TimeFormat from './TimeFormat';
|
|
3
|
+
|
|
4
|
+
function stripTags(input, allowed) {
|
|
5
|
+
allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('')
|
|
6
|
+
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi
|
|
7
|
+
var commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi
|
|
8
|
+
return input.replace(commentsAndPhpTags, '').replace(tags, function($0, $1) {
|
|
9
|
+
return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
var SRT_STATE_SUBNUMBER = 0,
|
|
14
|
+
SRT_STATE_TIME = 1,
|
|
15
|
+
SRT_STATE_TEXT = 2,
|
|
16
|
+
SRT_STATE_BLANK = 3;
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
parse: function(lines) {
|
|
20
|
+
var result = [],
|
|
21
|
+
state = SRT_STATE_SUBNUMBER,
|
|
22
|
+
subText = '',
|
|
23
|
+
subTime = '';
|
|
24
|
+
|
|
25
|
+
function time(subTime) {
|
|
26
|
+
|
|
27
|
+
var range = subTime.split(' --> ');
|
|
28
|
+
if (!TimeFormat.isValidTime(range[0]) || !TimeFormat.isValidTime(range[1])) {
|
|
29
|
+
throw new Error('Malformed SRT file');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
startTime: range[0],
|
|
34
|
+
stopTime: range[1]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
lines.forEach(function(line) {
|
|
39
|
+
line = line.replace(/^\s+|\s+$/g, '');
|
|
40
|
+
|
|
41
|
+
switch (state) {
|
|
42
|
+
case SRT_STATE_SUBNUMBER:
|
|
43
|
+
state = SRT_STATE_TIME;
|
|
44
|
+
break;
|
|
45
|
+
|
|
46
|
+
case SRT_STATE_TIME:
|
|
47
|
+
subTime = line;
|
|
48
|
+
state = SRT_STATE_TEXT;
|
|
49
|
+
break;
|
|
50
|
+
|
|
51
|
+
case SRT_STATE_TEXT:
|
|
52
|
+
if (line !== '') {
|
|
53
|
+
subText += (subText) ? "\n" + line : line;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
result.push(Object.assign(time(subTime), {
|
|
57
|
+
text: subText
|
|
58
|
+
}));
|
|
59
|
+
subText = '';
|
|
60
|
+
state = SRT_STATE_SUBNUMBER;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return result;
|
|
66
|
+
},
|
|
67
|
+
export: function(lines) {
|
|
68
|
+
var result = '',
|
|
69
|
+
i = 1;
|
|
70
|
+
|
|
71
|
+
lines.forEach(function(line) {
|
|
72
|
+
if (line.text === undefined) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Replace <br> tags with newlines. These tags may accidentally be stored in the translation
|
|
77
|
+
let text = _.unescape(line.text.replace(/<br>/g, '\n'));
|
|
78
|
+
|
|
79
|
+
// Remove any tags that may have made it into the translation (div tags seem to be the main problem).
|
|
80
|
+
// <b> <i> are the only two supported tags currently
|
|
81
|
+
text = stripTags(text, '<b><i>');
|
|
82
|
+
|
|
83
|
+
result += (i++) + "\n";
|
|
84
|
+
result += TimeFormat.startTime(line) + " --> " + TimeFormat.stopTime(line) + "\n";
|
|
85
|
+
result += text + "\n\n";
|
|
86
|
+
});
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
};
|