@soga/encoder 0.0.6 → 0.0.7

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.
Files changed (43) hide show
  1. package/dist/encoder.d.ts +27 -0
  2. package/dist/encoder.js +1 -0
  3. package/dist/main.d.ts +3 -18
  4. package/dist/main.js +1 -1
  5. package/dist/types/main.d.ts +20 -1
  6. package/dist/types/main.js +1 -1
  7. package/package.json +5 -12
  8. package/dist/common/common.d.ts +0 -20
  9. package/dist/common/common.js +0 -1
  10. package/dist/constants.d.ts +0 -10
  11. package/dist/constants.js +0 -1
  12. package/dist/img-encoder/index.d.ts +0 -12
  13. package/dist/img-encoder/index.js +0 -1
  14. package/dist/media-encoder/audio-cover.d.ts +0 -6
  15. package/dist/media-encoder/audio-cover.js +0 -1
  16. package/dist/media-encoder/audio-separator.d.ts +0 -6
  17. package/dist/media-encoder/audio-separator.js +0 -1
  18. package/dist/media-encoder/audio-transcoder.d.ts +0 -8
  19. package/dist/media-encoder/audio-transcoder.js +0 -1
  20. package/dist/media-encoder/index.d.ts +0 -6
  21. package/dist/media-encoder/index.js +0 -1
  22. package/dist/media-encoder/media-base.d.ts +0 -48
  23. package/dist/media-encoder/media-base.js +0 -1
  24. package/dist/media-encoder/media-grouper.d.ts +0 -67
  25. package/dist/media-encoder/media-grouper.js +0 -1
  26. package/dist/media-encoder/media-text.d.ts +0 -11
  27. package/dist/media-encoder/media-text.js +0 -1
  28. package/dist/media-encoder/video-separator.d.ts +0 -5
  29. package/dist/media-encoder/video-separator.js +0 -1
  30. package/dist/media-encoder/video-thumbnailer.d.ts +0 -28
  31. package/dist/media-encoder/video-thumbnailer.js +0 -1
  32. package/dist/media-encoder/video-transcoder.d.ts +0 -6
  33. package/dist/media-encoder/video-transcoder.js +0 -1
  34. package/dist/prepare/prepare.d.ts +0 -13
  35. package/dist/prepare/prepare.js +0 -1
  36. package/dist/source-encoder/index.d.ts +0 -10
  37. package/dist/source-encoder/index.js +0 -1
  38. package/dist/txt-encoder/index.d.ts +0 -20
  39. package/dist/txt-encoder/index.js +0 -1
  40. package/dist/types/runtime.d.ts +0 -102
  41. package/dist/types/runtime.js +0 -1
  42. package/dist/utils/level.d.ts +0 -2
  43. package/dist/utils/level.js +0 -1
@@ -0,0 +1,27 @@
1
+ import { Repository } from 'typeorm';
2
+ import { EncoderParams } from './types/main';
3
+ import { UploadFile } from '@soga/entities';
4
+ import { WorkerPercent } from '@soga/types';
5
+ export declare class Encoder {
6
+ protected onProgress: (percent: WorkerPercent) => Promise<void>;
7
+ protected onSuccess: (file: UploadFile) => Promise<void>;
8
+ protected onError: (file: UploadFile) => Promise<void>;
9
+ protected onComplete: (file: UploadFile) => Promise<void>;
10
+ protected fileRepository: Repository<UploadFile>;
11
+ private isRunning;
12
+ private thread_count;
13
+ private maxThreads;
14
+ private threads;
15
+ params: EncoderParams;
16
+ protected getValidThreads(threads: number): number;
17
+ constructor(params: EncoderParams);
18
+ setThreads(threads: number): Promise<void>;
19
+ init(): Promise<void>;
20
+ start(): Promise<void>;
21
+ stopFiles(ids: number[]): Promise<void>;
22
+ stopAll(): Promise<void>;
23
+ repairFiles(ids: number[]): Promise<void>;
24
+ deleteFiles(ids: number[]): Promise<void>;
25
+ private getThread;
26
+ private run;
27
+ }
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Encoder=void 0;const typeorm_1=require("typeorm"),entities_1=require("@soga/entities"),types_1=require("@soga/types"),piscina_1=require("piscina"),single_encoder_1=require("@soga/single-encoder"),piscina=new piscina_1.Piscina({filename:require.resolve("@soga/single-encoder")});class Encoder{onProgress=async()=>{};onSuccess=async()=>{};onError=async()=>{};onComplete=async()=>{};fileRepository;isRunning=!1;thread_count=2;maxThreads=10;threads=[];params;getValidThreads(t){return Math.min(Math.max(t??0,0),this.maxThreads)}constructor(t){this.params=t,this.fileRepository=t.dataSource.getRepository(entities_1.UploadFile),this.thread_count=this.getValidThreads(t.threads??this.thread_count),t.onProgress&&(this.onProgress=t.onProgress.bind(this)),t.onSuccess&&(this.onSuccess=t.onSuccess.bind(this)),t.onError&&(this.onError=t.onError.bind(this)),t.onComplete&&(this.onComplete=t.onComplete.bind(this)),piscina.on("message",(t=>{this.onProgress(t)}))}async setThreads(t){const e=this.getValidThreads(t);this.thread_count=e,await this.run()}async init(){await this.fileRepository.update({uid:this.params.uid,encode_status:types_1.EncodeStatus.PROCESS},{encode_status:types_1.EncodeStatus.NULL})}async start(){await this.run()}async stopFiles(t){const e=this.threads.filter((e=>t.includes(e.id)));for(const t of e)await t.stop();await this.fileRepository.update({id:(0,typeorm_1.In)(t),is_paused:!1},{is_paused:!0})}async stopAll(){await this.fileRepository.update({uid:this.params.uid,is_folder:!1},{is_paused:!0}),this.thread_count=0,await this.run()}async repairFiles(t){await this.fileRepository.update({id:(0,typeorm_1.In)(t),encode_status:types_1.EncodeStatus.ERROR},{encode_status:types_1.EncodeStatus.NULL}),await this.run()}async deleteFiles(t){const e=this.threads.filter((e=>t.includes(e.id)));for(const t of e)await t.stop();await this.fileRepository.delete({id:(0,typeorm_1.In)(t)})}getThread(t){const e=new AbortController;return{id:t.id,uid:t.uid,start:async()=>{try{const s=await this.fileRepository.findOneBy({id:t.id});if(s.encode_status!==types_1.EncodeStatus.PROCESS)return;if(!s)return;const{id:i,uid:a,config:o,path:r,type:n,ftype:d,external_texts:p}=s,c=[];s.ali_host_id&&c.push(types_1.HostType.ALI),s.baidu_host_id&&c.push(types_1.HostType.BAIDU);const h=await this.params.getOutputRoot(s);await this.fileRepository.update(t.id,{output_root:h});const u={id:i,uid:a,keepPreview:o.file_keeps==types_1.RecordFileKeep.PREVIEW||o.file_keeps==types_1.RecordFileKeep.BOTH,keepSource:o.file_keeps==types_1.RecordFileKeep.SOURCE||o.file_keeps==types_1.RecordFileKeep.BOTH,filePath:r,type:n,ftype:d,hosts:c,outputRoot:h,texts:p,ffmpegPath:this.params.ffmpegPath};await(0,single_encoder_1.prepare)(u);const l=await piscina.run(u,{name:"encode",signal:e.signal});if(!l||!l.state)throw new Error("upload chunk failed!");const{img:_,media:y,source:f,txt:w}=l.data;await this.fileRepository.update(t.id,{encode_status:types_1.EncodeStatus.SUCCESS,img_data:_,media_data:y,source_data:f,txt_data:w}),await this.onSuccess(await this.fileRepository.findOneBy({id:i}))}catch(e){"AbortError"!==e.name?await this.onError(await this.fileRepository.findOneBy({id:t.id})):await this.fileRepository.update(t.id,{encode_status:types_1.EncodeStatus.NULL})}finally{await this.onComplete(await this.fileRepository.findOneBy({id:t.id})),this.threads=this.threads.filter((e=>e.id!==t.id)),await this.run()}},stop:async()=>{e.abort()}}}async run(){for(;this.isRunning;)await new Promise((t=>{setTimeout(t,200)}));this.isRunning=!0;const t=this.threads.length,e=this.thread_count;if(t<e){const s=e-t;for(let t=0;t<s;t++){const t=await this.fileRepository.findOne({where:{uid:this.params.uid,encode_status:types_1.EncodeStatus.NULL,is_folder:!1,is_paused:!1},order:{encode_status:"DESC",created_at:"ASC"}});if(!t)break;const e=this.getThread(t);this.threads.push(e),await this.fileRepository.update(t.id,{encode_status:types_1.EncodeStatus.PROCESS}),e.start()}}else if(t>e){const s=t-e,i=this.threads.slice(0,s);for(const t of i)await t.stop()}this.isRunning=!1}}exports.Encoder=Encoder;
package/dist/main.d.ts CHANGED
@@ -1,18 +1,3 @@
1
- import { Params } from './types/runtime';
2
- import { SimpleError } from '@soga/utils';
3
- import { EncodeResult } from './types/main';
4
- export declare const prepare: (params: Params) => Promise<void>;
5
- export declare const encode: (params: Params) => Promise<{
6
- state: boolean;
7
- data: EncodeResult;
8
- } | {
9
- state: boolean;
10
- data: {
11
- code: number;
12
- message: string;
13
- stack: string;
14
- };
15
- } | {
16
- state: boolean;
17
- data: SimpleError;
18
- }>;
1
+ import { Encoder } from './encoder';
2
+ import { EncoderParams } from './types/main';
3
+ export declare const getEncoder: (params: EncoderParams) => Promise<Encoder>;
package/dist/main.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.encode=exports.prepare=void 0;const fs_extra_1=require("fs-extra"),path_1=require("path"),source_encoder_1=require("./source-encoder"),txt_encoder_1=require("./txt-encoder"),img_encoder_1=require("./img-encoder"),media_encoder_1=require("./media-encoder"),utils_1=require("@soga/utils"),prepare_1=require("./prepare/prepare"),level_1=require("./utils/level"),prepare=async e=>{let t;try{await(0,fs_extra_1.ensureDir)(e.outputRoot),t=await(0,level_1.getEncoderLevel)(e.outputRoot,e.filePath);const r=new prepare_1.Prepare({...e,db:t});await r.start()}catch(e){throw new utils_1.SimpleError({code:10100,message:e.message,stack:e.stack})}finally{t?.close()}};exports.prepare=prepare;const encode=async e=>{let t=null;try{await(0,fs_extra_1.ensureDir)(e.outputRoot);const r={};t=await(0,level_1.getEncoderLevel)(e.outputRoot,e.filePath);const a=new prepare_1.Prepare({...e,db:t}),o=await a.getResult(),s={...e,keepPreview:o.options?.keep_preview,keepSource:o.options?.keep_source};if(a.isMedia&&s.keepPreview){const e=new media_encoder_1.MediaEncoder({...s,db:t}),a=await e.start();r.media=a}if(a.isTxt&&s.keepPreview){const e=new txt_encoder_1.TxtEncoder({...s,db:t}),a=await e.start();r.txt=a}if(a.isImg&&s.keepPreview){const e=new img_encoder_1.ImgEncoder({...s,db:t}),a=await e.start();r.img=a}if(s.keepSource){const e=new source_encoder_1.SourceEncoder({...s,db:t}),a=await e.start();r.source=a}return s.debug&&await saveDebugData(e,r),{state:!0,data:r}}catch(e){return e instanceof utils_1.SimpleError?{state:!1,data:{code:e.code,message:e.message,stack:e.stack}}:{state:!1,data:new utils_1.SimpleError({code:1e4,message:e.message,stack:e.stack})}}finally{t?.close()}};async function saveDebugData(e,t){const r=(0,path_1.resolve)(e.outputRoot,".debug-output.json");await(0,fs_extra_1.writeJSON)(r,t,{spaces:2})}exports.encode=encode;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getEncoder=void 0;const encoder_1=require("./encoder"),instanceMap=new Map,getEncoder=async e=>{if(!instanceMap.has(e.uid)){const n=new encoder_1.Encoder(e);await n.init(),instanceMap.set(e.uid,n)}return instanceMap.get(e.uid)};exports.getEncoder=getEncoder;
@@ -1 +1,20 @@
1
- export { UploadProcessStep as ProcessStep, PrepareResult, MediaGroupAudioItem, MediaGroupVideoItem, MediaGroupThumbnailInfo, MediaGroupCoverInfo, FilePartItem, MediaGroupData, SourceGroupData, TxtGroupData, WorkerPercent, WorkerError, ImgGroupData, EncodeResult, } from '@soga/types';
1
+ import { DataSource } from 'typeorm';
2
+ import { UploadFile } from '@soga/entities';
3
+ export type EncoderParams = {
4
+ dataSource: DataSource;
5
+ ffmpegPath: string;
6
+ uid: number;
7
+ getOutputRoot: (file: UploadFile) => Promise<string>;
8
+ threads?: number;
9
+ debug?: boolean;
10
+ onProgress?: (percent: number) => Promise<void>;
11
+ onSuccess?: (file: UploadFile) => Promise<void>;
12
+ onError?: (file: UploadFile) => Promise<void>;
13
+ onComplete?: (file: UploadFile) => Promise<void>;
14
+ };
15
+ export type ThreadType = {
16
+ id: number;
17
+ uid: number;
18
+ start: () => Promise<void>;
19
+ stop: () => Promise<void>;
20
+ };
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ProcessStep=void 0;var types_1=require("@soga/types");Object.defineProperty(exports,"ProcessStep",{enumerable:!0,get:function(){return types_1.UploadProcessStep}});
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soga/encoder",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -22,14 +22,13 @@
22
22
  "prepublishOnly": "npm run build"
23
23
  },
24
24
  "devDependencies": {
25
- "@types/fluent-ffmpeg": "^2.1.27",
26
25
  "@types/fs-extra": "^11.0.4",
27
26
  "@types/glob": "^8.1.0",
28
27
  "@types/jest": "^29.5.4",
29
- "@types/langdetect": "^0.2.2",
30
28
  "@types/node": "^20.8.7",
31
29
  "@typescript-eslint/eslint-plugin": "^6.4.1",
32
30
  "@typescript-eslint/parser": "^6.4.1",
31
+ "better-sqlite3": "^11.8.1",
33
32
  "eslint": "^8.47.0",
34
33
  "eslint-config-prettier": "^9.0.0",
35
34
  "eslint-plugin-jest": "^27.2.3",
@@ -47,16 +46,10 @@
47
46
  "author": "",
48
47
  "license": "ISC",
49
48
  "dependencies": {
50
- "@soga/common": "^0.1.6",
51
- "@soga/mediainfo": "^0.0.7",
49
+ "@soga/entities": "^0.0.12",
50
+ "@soga/single-encoder": "^0.0.1",
52
51
  "@soga/types": "^0.0.61",
53
- "@soga/utils": "^0.0.13",
54
- "chardet": "^2.0.0",
55
- "fluent-ffmpeg": "^2.1.3",
56
52
  "fs-extra": "^11.2.0",
57
- "iconv-lite": "^0.6.3",
58
- "langdetect": "^0.2.1",
59
- "level": "^9.0.0",
60
- "sharp": "^0.33.5"
53
+ "typeorm": "^0.3.20"
61
54
  }
62
55
  }
@@ -1,20 +0,0 @@
1
- import { EncoderParams } from '../types/runtime';
2
- import { WorkerError, WorkerPercent } from '../types/main';
3
- import { WorkerEncodePrepare } from '@soga/types';
4
- export declare class Common {
5
- protected get file_size_limit(): number;
6
- params: EncoderParams;
7
- private filesize;
8
- getFilesize(): Promise<number>;
9
- constructor(params: EncoderParams);
10
- postProgress(progress: {
11
- type: string;
12
- percent: number;
13
- }): Promise<void>;
14
- throwError(error: {
15
- code: number;
16
- message: string;
17
- stack?: string;
18
- }): Promise<void>;
19
- protected postMessage(data: WorkerPercent | WorkerError | WorkerEncodePrepare): Promise<void>;
20
- }
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Common=void 0;const worker_threads_1=require("worker_threads"),utils_1=require("@soga/utils");class Common{get file_size_limit(){return this.params.maxPartSize||4294967296}params;filesize=0;async getFilesize(){if(this.filesize)return this.filesize;const e=await(0,utils_1.getFileSize)(this.params.filePath);this.filesize=e}constructor(e){this.params=e}async postProgress(e){const s={step:e.type,percent:e.percent};await this.postMessage({id:this.params.id,type:"percent",data:s})}async throwError(e){throw new utils_1.SimpleError(e)}async postMessage(e){worker_threads_1.parentPort&&worker_threads_1.parentPort.postMessage(e)}}exports.Common=Common;
@@ -1,10 +0,0 @@
1
- export declare const AUDIO_TRACKS_CONFIG = "audio_tracks_config";
2
- export declare const VIDEO_M3U8_INFO = "video_m3u8_info";
3
- export declare const AUDIO_TRANSCODER_DATA = "audio_transcoder_data";
4
- export declare const AUDIO_COVER_DATA = "audio_cover_data";
5
- export declare const VIDEO_TRANSCODER_DATA = "video_transcoder_data";
6
- export declare const MEDIA_TEXT_DATA = "media_text_data";
7
- export declare const VIDEO_THUMBNAIL_DATA = "video_thumbnail_data";
8
- export declare const VIDEO_COVER_DATA = "video_cover_data";
9
- export declare const MEDIA_GROUPED_DATA = "media_grouped_data";
10
- export declare const PREPARE_RESULT = "prepare_result";
package/dist/constants.js DELETED
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.PREPARE_RESULT=exports.MEDIA_GROUPED_DATA=exports.VIDEO_COVER_DATA=exports.VIDEO_THUMBNAIL_DATA=exports.MEDIA_TEXT_DATA=exports.VIDEO_TRANSCODER_DATA=exports.AUDIO_COVER_DATA=exports.AUDIO_TRANSCODER_DATA=exports.VIDEO_M3U8_INFO=exports.AUDIO_TRACKS_CONFIG=void 0,exports.AUDIO_TRACKS_CONFIG="audio_tracks_config",exports.VIDEO_M3U8_INFO="video_m3u8_info",exports.AUDIO_TRANSCODER_DATA="audio_transcoder_data",exports.AUDIO_COVER_DATA="audio_cover_data",exports.VIDEO_TRANSCODER_DATA="video_transcoder_data",exports.MEDIA_TEXT_DATA="media_text_data",exports.VIDEO_THUMBNAIL_DATA="video_thumbnail_data",exports.VIDEO_COVER_DATA="video_cover_data",exports.MEDIA_GROUPED_DATA="media_grouped_data",exports.PREPARE_RESULT="prepare_result";
@@ -1,12 +0,0 @@
1
- import { Common } from '../common/common';
2
- import { ImgGroupData } from '../types/main';
3
- export declare class ImgEncoder extends Common {
4
- private getCompressFileName;
5
- private getCompressFilePath;
6
- private getGzFileName;
7
- private getGzFilePath;
8
- start(): Promise<ImgGroupData>;
9
- private getData;
10
- private compress;
11
- private gzip;
12
- }
@@ -1 +0,0 @@
1
- "use strict";var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.ImgEncoder=void 0;const path_1=require("path"),common_1=require("../common/common"),sharp_1=__importDefault(require("sharp")),utils_1=require("@soga/utils"),main_1=require("../types/main"),types_1=require("@soga/types");class ImgEncoder extends common_1.Common{getCompressFileName(){return"img_compress.png"}getCompressFilePath(){return(0,path_1.resolve)(this.params.outputRoot,this.getCompressFileName())}getGzFileName(){return"preview_0"}getGzFilePath(){return(0,path_1.resolve)(this.params.outputRoot,this.getGzFileName())}async start(){try{return await this.compress(),await this.gzip(),await this.getData()}catch(t){await this.throwError({code:10400,message:t.message,stack:t.stack})}}async getData(){const t="img_encoder_get_data",e=await this.params.db.get(t);if(e)return e;const a=this.params.filePath,i=await(0,utils_1.getFileSize)(a),{width:s,height:r}=await(0,sharp_1.default)(a).metadata(),h=this.getCompressFilePath(),{width:o,height:p}=await(0,sharp_1.default)(h).metadata(),m=await(0,utils_1.getFileSize)(h),l=this.getGzFilePath(),n=await(0,utils_1.getFileSize)(l),u=await(0,utils_1.calculateMd5)({file:l,start:0,end:i-1}),c={start:0,end:n-1,size:n,file:this.getGzFileName(),path:l,index:0,md5:u};if(this.params.hosts.includes(types_1.HostType.BAIDU)){const t=await(0,utils_1.calculateMd4)({file:l,start:0,end:n-1});c.md4=t}if(this.params.hosts.includes(types_1.HostType.ALI)){const t=await(0,utils_1.calculateSha1)({file:l,start:0,end:n-1});c.sha1=t}const g={meta:{width:s,height:r,size:i,t_width:o,t_height:p,t_size:m},parts:[c]};return await this.params.db.put(t,g),g}async compress(){const t="img_encoder_compress";if(await this.params.db.get(t))return;const e=this.getCompressFilePath(),a=await(0,sharp_1.default)(this.params.filePath).metadata();if(a.width/a.height>1){const t=Math.min(640,a.height);await(0,sharp_1.default)(this.params.filePath).resize({height:t}).png({quality:75}).toFile(e)}else{const t=Math.min(640,a.width);await(0,sharp_1.default)(this.params.filePath).resize({width:t}).png({quality:75}).toFile(e)}await this.params.db.put(t,!0)}async gzip(){const t="img_encoder_gzip";if(await this.params.db.get(t))return;const e=this.getCompressFilePath(),a=this.getGzFilePath();await(0,utils_1.gzip)(e,a),await this.params.db.put(t,!0),await this.postProgress({type:main_1.ProcessStep.transcode_img,percent:1})}}exports.ImgEncoder=ImgEncoder;
@@ -1,6 +0,0 @@
1
- import { MediaBase } from './media-base';
2
- export declare class AudioCover extends MediaBase {
3
- getAudioCoverName(): string;
4
- start(): Promise<void>;
5
- private generateCover;
6
- }
@@ -1 +0,0 @@
1
- "use strict";var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.AudioCover=void 0;const path_1=require("path"),media_base_1=require("./media-base"),fs_extra_1=require("fs-extra"),sharp_1=__importDefault(require("sharp")),utils_1=require("@soga/utils"),constants_1=require("../constants"),main_1=require("../types/main");class AudioCover extends media_base_1.MediaBase{getAudioCoverName(){return"audio_cover.png"}async start(){try{await this.generateCover(),await this.postProgress({type:main_1.ProcessStep.transcode_thumbnail,percent:1})}catch(t){await this.throwError({code:11100,message:t.message,stack:t.stack})}}async generateCover(){const t=constants_1.AUDIO_COVER_DATA,e=await this.params.db.get(t);if(e)return e;if(0==e)return!1;try{const e=this.getAudioCoverName(),a=["-i",this.params.filePath,"-map","0:v","-y",e];await this.ffmpeg(a,{cwd:this.params.outputRoot});const r=(0,path_1.resolve)(this.params.outputRoot,e),s=await(0,fs_extra_1.pathExists)(r),i=await(0,sharp_1.default)(r).metadata();if(s){const a={cover_name:e,cover_path:r,size:await(0,utils_1.getFileSize)(r),width:i.width??0,height:i.height??0};return await this.params.db.put(t,a),a}}catch(e){await this.params.db.put(t,!1)}return!1}}exports.AudioCover=AudioCover;
@@ -1,6 +0,0 @@
1
- import { MediaBase } from './media-base';
2
- export declare class AudioSeparator extends MediaBase {
3
- start(): Promise<void>;
4
- private separateTracks;
5
- private separateAudioTrack;
6
- }
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.AudioSeparator=void 0;const path_1=require("path"),media_base_1=require("./media-base"),constants_1=require("../constants"),utils_1=require("@soga/utils"),mediainfo_1=require("@soga/mediainfo"),main_1=require("../types/main");class AudioSeparator extends media_base_1.MediaBase{async start(){try{await this.separateTracks()}catch(a){await this.throwError({code:10600,message:a.message,stack:a.stack})}}async separateTracks(){const a="separate_audio_tracks";if(await this.params.db.get(a))return;const e=await this.getMediainfo(),{audio:t}=e;if(!t?.length)return;const s=t.length,i=[];for(let a=0;a<s;a++){const e=t[a],{codec:o,channels:d,lossless:c}=e,r={adapt:{need:!0,codec:"",channels:Math.min(d,2)},high:{need:!1,codec:"flac",channels:0}};(0,mediainfo_1.isAudioAdapt)(o)?d>2?(r.high.need=!0,r.high.codec="copy",r.adapt.codec=o,r.adapt.need=!0):(r.adapt.need=!0,r.adapt.codec="copy",r.adapt.channels=0):(0,mediainfo_1.isAudioHigh)(o)?(r.high.need=!0,r.high.codec="copy",r.adapt.need=!0,r.adapt.codec="aac"):(r.adapt.need=!0,r.adapt.codec="aac",c&&(r.high.need=!0,r.high.codec="flac")),r.adapt.need&&await this.separateAudioTrack(a,{type:"adapt",codec:r.adapt.codec,tracks:s,channels:r.adapt.channels,need_adapt:!0,need_high:r.high.need}),r.high.need&&await this.separateAudioTrack(a,{type:"high",codec:r.high.codec,tracks:s,channels:r.high.channels,need_adapt:r.adapt.need,need_high:!0}),i.push(r)}await this.params.db.put(constants_1.AUDIO_TRACKS_CONFIG,i),await this.params.db.put(a,!0)}async separateAudioTrack(a,e){const t=`separate_audio_${a}_${e.type}`;if(await this.params.db.get(t))return;const s=await this.getMediainfo(),i=s.audio[a];if(!i)return;const o=i?.duration??s.general.duration,d=this.getAudioName({type:"formatted",track_order:a,quality:e.type}),c=(0,path_1.resolve)(this.params.outputRoot,d),r=["-sn","-vn","-map",`0:a:${a}`];e.codec&&r.push("-c:a",e.codec),e.channels&&r.push("-ac",e.channels.toString()),r.push("-y"),await new Promise(((t,s)=>{const i=this.getFluent().input(this.params.filePath).outputOptions(r).on("progress",(async t=>{if("copy"!=e.codec){const s=this.parseProgress(t,o);let i=a;"adapt"==e.type?e.need_high?i+=1/3*s:i+=s:e.need_adapt?(i+=1/3,i+=2/3*s):i+=s;const d=i/e.tracks;await this.postProgress({type:main_1.ProcessStep.separate_audio,percent:d})}})).on("end",(async()=>{t("end"),i.kill("SIGKILL")})).on("error",(async a=>{s(a)})).output(c);i.run()}));if(!await(0,utils_1.isValidFile)(c))throw new Error(`Separate audio track failed: [track_order:${a}, track_quality ${e.type}] `);if("copy"==e.codec){let t=a;"adapt"==e.type&&e.need_high?t+=1/3:t+=1;const s=t/e.tracks;await this.postProgress({type:main_1.ProcessStep.separate_audio,percent:s})}await this.params.db.put(t,!0)}}exports.AudioSeparator=AudioSeparator;
@@ -1,8 +0,0 @@
1
- import { MediaBase } from './media-base';
2
- export declare class AudioTranscoder extends MediaBase {
3
- start(): Promise<void>;
4
- saveData(): Promise<void>;
5
- private getQualityData;
6
- private transcodeAudios;
7
- private transcodeAudio;
8
- }
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.AudioTranscoder=void 0;const media_base_1=require("./media-base"),constants_1=require("../constants"),path_1=require("path"),utils_1=require("@soga/utils"),mediainfo_1=require("@soga/mediainfo"),main_1=require("../types/main");class AudioTranscoder extends media_base_1.MediaBase{async start(){try{await this.transcodeAudios(),await this.saveData()}catch(t){await this.throwError({code:10700,message:t.message,stack:t.stack})}}async saveData(){const t=constants_1.AUDIO_TRANSCODER_DATA;if(await this.params.db.get(t))return;const a=await this.getMediainfo();if(!a?.audio?.length)return;const e=a.audio.length,i=await this.params.db.get(constants_1.AUDIO_TRACKS_CONFIG),s=[],o={};for(let t=0;t<e;t++){const a=i[t];a.adapt.need&&(o.adapt=await this.getQualityData({track_order:t,quality:"adapt"})),a.high.need&&(o.high=await this.getQualityData({track_order:t,quality:"high"})),s.push(o)}await this.params.db.put(t,s)}async getQualityData({track_order:t,quality:a}){const e=`audio_quality_data_${t}_${a}`,i=await this.params.db.get(e);if(i)return i;const s=this.getAudioName({type:"formatted",track_order:t,quality:a}),o=(0,path_1.resolve)(this.params.outputRoot,s),r=await(0,mediainfo_1.parseMediaInfo)(o);if(!r.audio?.length)throw new Error("parse mediainfo failed!");const d=r.audio[0],n=this.getAudioName({type:"manifest",track_order:t,quality:a}),u=await(0,utils_1.getFileSize)(o),c=(0,path_1.resolve)(this.params.outputRoot,n),h=await(0,mediainfo_1.getMp4boxInfo)(o),{audios:p}=h,{language:_,codec:l}=p[0],m=_&&!_.startsWith("un")?{language:_}:void 0,{data:g}=await(0,mediainfo_1.parseM3U8)(c),{bandwidth:w,average_bandwidth:y}=this.calculateBandwidth(g),f={m3u8:g,m3u8_path:c,codec:l,codec_name:d.codec,...m,order:t,lossless:d.lossless,channels:d.channels,bandwidth:w,size:u,average_bandwidth:y};return await this.params.db.put(e,f),f}async transcodeAudios(){const t="transcode-audios";if(await this.params.db.get(t))return;const a=await this.getMediainfo();if(!a?.audio?.length)return;const e=a.audio.length,i=await this.params.db.get(constants_1.AUDIO_TRACKS_CONFIG);for(let t=0;t<e;t++){const a=i[t];a.adapt.need&&await this.transcodeAudio(t,{...a.adapt,type:"adapt"}),a.high.need&&await this.transcodeAudio(t,{...a.high,type:"high"});const s=(t+1)/e;await this.postProgress({type:main_1.ProcessStep.transcode_audio,percent:s})}await this.params.db.put(t,!0)}async transcodeAudio(t,a){const e=`transcode-audio-${t}-${a.type}`;if(await this.params.db.get(e))return;const i=this.getAudioName({type:"formatted",track_order:t,quality:a.type}),s=this.getAudioName({type:"init",track_order:t,quality:a.type}),o=this.getAudioName({type:"manifest",track_order:t,quality:a.type}),r=["-i",i,"-vn","-sn","-c:a","copy","-hls_list_size","0","-hls_time","6","-hls_segment_type","fmp4","-hls_fmp4_init_filename",s,"-y",o];await this.ffmpeg(r,{cwd:this.params.outputRoot});const d=(0,path_1.resolve)(this.params.outputRoot,o);if(!await(0,utils_1.isValidFile)(d))throw new Error(`Transcode audio track failed: [track_order:${t}, track_quality ${a.type}] `);await this.params.db.put(e,!0)}}exports.AudioTranscoder=AudioTranscoder;
@@ -1,6 +0,0 @@
1
- import { MediaBase } from './media-base';
2
- import { MediaGroupData } from '../types/main';
3
- export declare class MediaEncoder extends MediaBase {
4
- start(): Promise<MediaGroupData>;
5
- getResult(): Promise<MediaGroupData>;
6
- }
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.MediaEncoder=void 0;const media_base_1=require("./media-base"),video_separator_1=require("./video-separator"),audio_separator_1=require("./audio-separator"),video_transcoder_1=require("./video-transcoder"),audio_transcoder_1=require("./audio-transcoder"),media_text_1=require("./media-text"),video_thumbnailer_1=require("./video-thumbnailer"),audio_cover_1=require("./audio-cover"),media_grouper_1=require("./media-grouper"),constants_1=require("../constants");class MediaEncoder extends media_base_1.MediaBase{async start(){const e=await this.getMediainfo();if(!e)return;const a=e.video,r=a?.length,t=e.audio,i=t?.length;if(!i&&!r)return;const s=new media_text_1.MediaText(this.params);if(await s.start(),i){const e=new audio_separator_1.AudioSeparator(this.params);await e.start();const a=new audio_transcoder_1.AudioTranscoder(this.params);await a.start()}if(r){const e=new video_separator_1.VideoSeparator(this.params);await e.start();const a=new video_transcoder_1.VideoTranscoder(this.params);await a.start();const r=new video_thumbnailer_1.VideoThumbnailer(this.params);await r.start()}else if(i){const e=new audio_cover_1.AudioCover(this.params);await e.start()}const o=new media_grouper_1.MediaGrouper(this.params);return await o.start(),await this.getResult()}async getResult(){return await this.params.db.get(constants_1.MEDIA_GROUPED_DATA)}}exports.MediaEncoder=MediaEncoder;
@@ -1,48 +0,0 @@
1
- import { EncoderParams } from '../types/runtime';
2
- import Ffmpeg from 'fluent-ffmpeg';
3
- import { SpawnOptions } from 'child_process';
4
- import { Common } from '../common/common';
5
- import { Mediainfo, type M3U8Manifest } from '@soga/mediainfo';
6
- export declare class MediaBase extends Common {
7
- private mediainfo;
8
- protected getMediainfo(): Promise<Mediainfo>;
9
- constructor(params: EncoderParams);
10
- protected getVideoManifestName({ width, height, }: {
11
- width: number;
12
- height: number;
13
- }): string;
14
- protected getVideoInitName({ width, height, }: {
15
- width: number;
16
- height: number;
17
- }): string;
18
- protected getFormattedVideoName({ width, height, }: {
19
- width: number;
20
- height: number;
21
- }): string;
22
- protected getAudioName({ type, track_order, quality, }: {
23
- type: 'init' | 'manifest' | 'formatted';
24
- track_order: number;
25
- quality: 'high' | 'adapt';
26
- }): string;
27
- protected getTextName({ position, ext, track_order, }: {
28
- position: 'builtin' | 'external';
29
- ext: 'srt' | 'vtt' | 'ass' | 'lrc';
30
- track_order: number;
31
- }): string;
32
- getVideoM3U8Info(): Promise<{
33
- duration: number;
34
- data: M3U8Manifest;
35
- input: string;
36
- }>;
37
- getFluent(): Ffmpeg.FfmpegCommand;
38
- protected ffmpeg(args: string[], options?: SpawnOptions): Promise<unknown>;
39
- protected calculateBandwidth(data: M3U8Manifest): {
40
- bandwidth: number;
41
- average_bandwidth: number;
42
- };
43
- protected parseProgress(progress: {
44
- percent?: number;
45
- timemark: string;
46
- }, duration: number): number;
47
- private parseSecond;
48
- }
@@ -1 +0,0 @@
1
- "use strict";var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.MediaBase=void 0;const fluent_ffmpeg_1=__importDefault(require("fluent-ffmpeg")),child_process_1=require("child_process"),constants_1=require("../constants"),path_1=require("path"),common_1=require("../common/common"),mediainfo_1=require("@soga/mediainfo");class MediaBase extends common_1.Common{mediainfo;async getMediainfo(){if(this.mediainfo)return this.mediainfo;const e="mediainfo",t=await this.params.db.get(e);if(t)return this.mediainfo=t,this.mediainfo;const i=this.params.filePath,a=await(0,mediainfo_1.parseMediaInfo)(i);return this.mediainfo=a,await this.params.db.put(e,a),this.mediainfo}constructor(e){super(e),fluent_ffmpeg_1.default.setFfmpegPath(e.ffmpegPath)}getVideoManifestName({width:e,height:t}){return`video-hls-${e}-${t}.m3u8`}getVideoInitName({width:e,height:t}){return`video-init-${e}-${t}.mp4`}getFormattedVideoName({width:e,height:t}){return`formatted-video-${e}-${t}.mp4`}getAudioName({type:e,track_order:t,quality:i}){return`audio-${t}-${i}-${e}.${"manifest"===e?"m3u8":"mp4"}`}getTextName({position:e,ext:t,track_order:i}){return`text-${e}-${i}.${t}`}async getVideoM3U8Info(){const e=constants_1.VIDEO_M3U8_INFO,t=await this.params.db.get(e);if(t)return t;const i=this.mediainfo?.video;if(!i?.length)return null;const a=i[0],r=this.getVideoManifestName({width:a.width,height:a.height}),n=(0,path_1.resolve)(this.params.outputRoot,r),s=await(0,mediainfo_1.parseM3U8)(n);return s?(await this.params.db.put(e,s),s):null}getFluent(){return(0,fluent_ffmpeg_1.default)()}async ffmpeg(e,t){const i=this.params.ffmpegPath;return await new Promise(((a,r)=>{const n=(0,child_process_1.spawn)(i,e,Object.assign({stdio:"ignore"},t));n.on("close",(e=>{a(e)})),n.on("error",(e=>{r(e)}))}))}calculateBandwidth(e){let t=0,i=0,a=0;e.segments.forEach((e=>{i+=e.duration,t+=e.size;const r=8*e.size/e.duration;r>a&&(a=r)}));const r=8*t/i;return{bandwidth:Math.ceil(a),average_bandwidth:Math.ceil(r)}}parseProgress(e,t){if("number"==typeof e.percent)return e.percent/100;return this.parseSecond(e.timemark)/t}parseSecond(e){const t=e.split(":").map(parseFloat),[i=0,a=0,r=0]=t.reverse();return 3600*r+60*a+i}}exports.MediaBase=MediaBase;
@@ -1,67 +0,0 @@
1
- import { MediaBase } from './media-base';
2
- import type { M3U8Manifest } from '@soga/mediainfo';
3
- export declare class MediaGrouper extends MediaBase {
4
- private group_data_key;
5
- private file_map_key;
6
- private media_parts_key;
7
- private safe_index;
8
- private current_position;
9
- private current_index;
10
- private group_data;
11
- private file_map;
12
- private getFileName;
13
- private getAudioManifestName;
14
- private getGroupedVideoManifestName;
15
- private getThumbnailInfoFilename;
16
- start(): Promise<void>;
17
- protected calculatePartInfo({ file }: {
18
- file: string;
19
- }): Promise<{
20
- size: number;
21
- md5: string;
22
- md4?: string;
23
- sha1?: string;
24
- start: number;
25
- end: number;
26
- }>;
27
- private getPartData;
28
- private getAudioData;
29
- private getVideoData;
30
- private getTextData;
31
- private getThumbnailData;
32
- private getMetaData;
33
- private getCoverData;
34
- private saveData;
35
- processGroups(): Promise<void>;
36
- private processOneGroup;
37
- private insertSafeFile;
38
- private getAudioTracks;
39
- private getVideoTracks;
40
- private getThumbnailInfo;
41
- private getCoverInfo;
42
- private getSubtitleInfo;
43
- private calculateData;
44
- private calculateSubtitles;
45
- private calculateCovers;
46
- calculateMediaTracks(): Promise<void>;
47
- private calculateThumbnails;
48
- calculateOneTrack(manifest: M3U8Manifest): Promise<string>;
49
- private writeTrackText;
50
- private writeText;
51
- private addOne;
52
- private safelyAddOne;
53
- private generateSafeFile;
54
- private removeFirst16Bytes;
55
- private calculateMd5;
56
- }
57
- export type FilePartItem = {
58
- start: number;
59
- end: number;
60
- size: number;
61
- file: string;
62
- path: string;
63
- index?: number;
64
- md4?: string;
65
- md5?: string;
66
- sha1?: string;
67
- };
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.MediaGrouper=void 0;const crypto_1=require("crypto"),media_base_1=require("./media-base"),path_1=require("path"),fs_extra_1=require("fs-extra"),constants_1=require("../constants"),main_1=require("../types/main"),utils_1=require("@soga/utils"),utils_2=require("@soga/utils"),types_1=require("@soga/types");class MediaGrouper extends media_base_1.MediaBase{group_data_key="group_data_key";file_map_key="file_map_key";media_parts_key="media_parts_key";safe_index=0;current_position=0;current_index=0;group_data=[];file_map={};getFileName=t=>`preview_${t}`;getAudioManifestName(t,e){return`group_audio_${e}_${t}.m3u8`}getGroupedVideoManifestName(t){return`group_video_${t}.m3u8`}getThumbnailInfoFilename(){return"thumbnail_info.json"}async start(){try{await this.calculateData(),await this.processGroups(),await this.saveData()}catch(t){await this.throwError({code:11200,message:t.message,stack:t.stack})}}async calculatePartInfo({file:t}){const e=(0,path_1.resolve)(this.params.outputRoot,t),a=await(0,utils_1.getFileSize)(e),i={size:a,md5:await(0,utils_2.calculateMd5)({file:e,start:0,end:a-1}),start:0,end:a-1};return this.params.hosts.includes(types_1.HostType.BAIDU)&&(i.md4=await(0,utils_2.calculateMd4)({file:e,start:0,end:a-1})),this.params.hosts.includes(types_1.HostType.ALI)&&(i.sha1=await(0,utils_2.calculateSha1)({file:e,start:0,end:a-1})),i}async getPartData(){const t=await this.params.db.get(this.media_parts_key),e=[];for(let a=0;a<t.length;a++){const i=t[a],s=(0,path_1.resolve)(this.params.outputRoot,i),n=await this.calculatePartInfo({file:i});e.push({file:i,path:s,...n})}return e}async getAudioData(){const t=await this.getAudioTracks();if(!t?.length)return;const e=[];return t.forEach(((t,a)=>{if(t.adapt){const{average_bandwidth:i,bandwidth:s,channels:n,codec:r,codec_name:o,language:h,order:c}=t.adapt,l=this.getAudioManifestName("adapt",a),d=this.file_map[l],{file_index:u,start:_,end:p,size:f,hash:g}=d,m={average_band:i,band:s,channels:n,codec:r,codec_name:o,language:h,order:c,file:this.getFileName(u),start:_,end:p,size:f,hash:g};if(t.high){const{average_bandwidth:e,bandwidth:i,channels:s,codec:n,codec_name:r,language:o,order:h}=t.high,c=this.getAudioManifestName("high",a),l=this.file_map[c],{file_index:d,start:u,end:_,size:p,hash:f}=l,g=this.getFileName(d);m.high={average_band:e,band:i,channels:s,codec:n,codec_name:r,language:o,order:h,file:g,start:u,end:_,size:p,hash:f}}e.push(m)}})),e}async getVideoData(){const t=await this.getVideoTracks();if(!t?.length)return;const e=[];return t.forEach(((t,a)=>{const i=this.getGroupedVideoManifestName(a),s=this.file_map[i],{file_index:n,start:r,end:o,size:h,hash:c}=s,l=this.getFileName(n),{average_bandwidth:d,bandwidth:u,codec:_,width:p,height:f,label:g,codec_name:m,bitdepth:w,framerate:y}=t,b={average_band:d,band:u,codec:_,codec_name:m,bitdepth:w,framerate:Math.round(y),width:p,height:f,file:l,start:r,end:o,size:h,label:g,hash:c};e.push(b)})),e}async getTextData(){const t=await this.getSubtitleInfo();if(!t?.length)return;const e=[];return t.forEach((t=>{const{builtin:a,file:i,lang:s,title:n,type:r,source:o}=t,h=this.file_map[i],{file_index:c,start:l,end:d,size:u}=h,_={builtin:a,lang:s,title:n,type:r,file:this.getFileName(c),start:l,end:d,size:u};if(o){const{file:t,type:e}=o,a=this.file_map[t],{file_index:i,start:s,end:n,size:r}=a,h=this.getFileName(i);_.source={size:r,type:e,file:h,start:s,end:n}}e.push(_)})),e}async getThumbnailData(){const t=await this.getThumbnailInfo();if(!t)return;const{row:e,col:a,width:i,height:s}=t,n=this.getThumbnailInfoFilename(),r=this.file_map[n],{file_index:o,start:h,end:c,size:l}=r;return{row:e,col:a,width:i,height:s,file:this.getFileName(o),start:h,end:c,size:l}}async getMetaData(){const t={},e=await this.getAudioTracks(),a=await this.getVideoTracks();if(a?.length){const e=await this.getMediainfo(),{duration:i}=e.general;if(a?.length){const{width:e,height:s,framerate:n,bitdepth:r,label:o,codec:h,codec_name:c}=a[0];t.video_meta={duration:i,bitdepth:r,width:e,height:s,framerate:n,label:o,codec:h,codec_name:c}}}if(e?.length){const a=e[0]?.high?e[0].high:e[0].adapt,{channels:i,lossless:s}=a,n=await this.getMediainfo(),{duration:r}=n.general,{codec:o,codec_name:h}=a;t.audio_meta={duration:r,channels:i,lossless:s,codec:o,codec_name:h}}return t}async getCoverData(){const t=await this.getCoverInfo();if(!t)return;const e=t,{cover_name:a,width:i,height:s}=e,n=this.file_map[a],{file_index:r,start:o,end:h,size:c}=n;return{width:i,height:s,file:this.getFileName(r),start:o,end:h,size:c}}async saveData(){const t=constants_1.MEDIA_GROUPED_DATA;if(await this.params.db.get(t))return;const e={parts:await this.getPartData()},a=await this.getAudioData();a&&(e.audios=a);const i=await this.getVideoData();i&&(e.videos=i);const s=await this.getTextData();s&&(e.texts=s);const n=await this.getThumbnailData();n&&(e.thumbnail=n);const r=await this.getCoverData();r&&(e.cover=r);const o=await this.getMetaData();o.audio_meta&&(e.audio_meta=o.audio_meta),o.video_meta&&(e.video_meta=o.video_meta),await this.params.db.put(t,e)}async processGroups(){const t="media_group_data";if(!await this.params.db.get(t)){for(;this.group_data.length;){const t=this.group_data.shift();await this.processOneGroup(t),await this.params.db.put(this.group_data_key,this.group_data)}await this.postProgress({type:main_1.ProcessStep.group_media,percent:1}),await this.params.db.put(t,!0)}}async processOneGroup(t){const e=[...t.files];await new Promise(((e,a)=>{(async()=>{const i=(0,path_1.resolve)(this.params.outputRoot,t.filename);await(0,fs_extra_1.remove)(i);let s=!1;const n=(0,fs_extra_1.createWriteStream)(i);n.on("open",(()=>{s=!0})).on("error",(async t=>{s&&n.close(),await(0,fs_extra_1.remove)(i),a(t)}));const r=async()=>{if(t.files.length){const e=t.files.shift(),a=t.files.length;await new Promise(((t,r)=>{const o=(0,fs_extra_1.createReadStream)(e);o.pipe(n,{end:!a}),o.on("end",(()=>{o.close(),t(!0)})),o.on("error",(async t=>{o.destroy(),s&&n.close(),await(0,fs_extra_1.remove)(i),r(t)}))})),await r()}};await r(),s&&n.end(),e(!0)})()}));const{length:a}=e;for(let t=0;t<a;t++){const a=e[t],i=(0,path_1.resolve)(this.params.outputRoot,a);this.params.debug||await(0,fs_extra_1.remove)(i)}}async insertSafeFile(){const t=await this.generateSafeFile();this.addOne(t,!0)}async getAudioTracks(){const t=await this.getMediainfo();if(!t.audio?.length)return[];return await this.params.db.get(constants_1.AUDIO_TRANSCODER_DATA)}async getVideoTracks(){const t=await this.getMediainfo();if(!t.video?.length)return[];return[await this.params.db.get(constants_1.VIDEO_TRANSCODER_DATA)]}async getThumbnailInfo(){const t=await this.getMediainfo();if(!t.video?.length)return null;const e=constants_1.VIDEO_THUMBNAIL_DATA;return await this.params.db.get(e)}async getCoverInfo(){const t=await this.getMediainfo();if(t.video?.length){const t=constants_1.VIDEO_COVER_DATA,e=await this.params.db.get(t);return e||null}if(t?.audio?.length){const t=constants_1.AUDIO_COVER_DATA,e=await this.params.db.get(t);return e||null}return null}async getSubtitleInfo(){const t=constants_1.MEDIA_TEXT_DATA,e=await this.params.db.get(t);return e||null}async calculateData(){const t="calculate_data";if(await this.params.db.get(t)){const t=await this.params.db.get(this.group_data_key);this.group_data=t;const e=await this.params.db.get(this.file_map_key);this.file_map=e}await this.insertSafeFile(),await this.calculateMediaTracks(),await this.calculateThumbnails(),this.current_index+=1,this.current_position=0,await this.insertSafeFile(),await this.calculateCovers(),await this.calculateSubtitles(),await this.params.db.put(this.group_data_key,this.group_data),await this.params.db.put(this.file_map_key,this.file_map),await this.params.db.put(t,!0);const e=this.group_data.map((t=>t.filename));await this.params.db.put(this.media_parts_key,e)}async calculateSubtitles(){const t=await this.getSubtitleInfo();if(!t)return;const{length:e}=t;for(let a=0;a<e;a++){const e=t[a],{file:i,file_path:s,size:n}=e,r=this.addOne({file_path:s,size:n});if(this.file_map[i]=r,e.source){const{file:t,file_path:a,size:i}=e.source,s=this.addOne({file_path:a,size:i});this.file_map[t]=s}}}async calculateCovers(){const t=await this.getCoverInfo();if(!t)return;const{cover_name:e,cover_path:a,size:i}=t,s=this.addOne({file_path:a,size:i});this.file_map[e]=s}async calculateMediaTracks(){const t=await this.getAudioTracks(),e=await this.getVideoTracks(),a=t.length;for(let e=0;e<a;e++){const a=t[e];if(a.adapt){const t=await this.calculateOneTrack(a.adapt.m3u8),i=this.getAudioManifestName("adapt",e),s=await this.writeTrackText(i,t);this.file_map[i]=s}if(a.high){const t=await this.calculateOneTrack(a.high.m3u8),i=this.getAudioManifestName("high",e),s=await this.writeTrackText(i,t);this.file_map[i]=s}}const i=e.length;for(let t=0;t<i;t++){const a=e[t],i=await this.calculateOneTrack(a.m3u8),s=this.getGroupedVideoManifestName(t),n=await this.writeTrackText(s,i);this.file_map[s]=n}}async calculateThumbnails(){const t=await this.getThumbnailInfo();if(!t)return;if(!t)return;const{tile_list:e}=t,{length:a}=e;let i=t.sprite_list;for(let t=0;t<a;t++){const a=e[t],s=a.file,n=await this.safelyAddOne({file_path:a.file_path,size:a.size});this.file_map[s]=n,i=i.map((t=>t.file===s?(t.file=this.getFileName(n.file_index),{...t,s:n.start,e:n.end}):t))}const s=this.getThumbnailInfoFilename(),n=await this.writeText(s,JSON.stringify(i));this.file_map[s]=n}async calculateOneTrack(t){const{segments:e,init:a,version:i,targetDuration:s,mediaSequence:n,endList:r}=t,o={},h=this.addOne({size:a.size,file_path:a.file_path}),c=["#EXTM3U",`#EXT-X-VERSION:${i}`,`#EXT-X-TARGETDURATION:${s}`,`#EXT-X-MEDIA-SEQUENCE:${n}`,`#EXT-X-MAP:URI="${this.getFileName(h.file_index)}",BYTERANGE="${h.size}@${h.start}"`];[...e].forEach((t=>{const{uri:e,size:a,duration:i,file_path:s}=t;o[e]=this.addOne({size:a,duration:i,file_path:s})})),Object.keys(o).forEach((t=>{const e=o[t];c.push(`#EXTINF:${e.duration},`),c.push(`#EXT-X-BYTERANGE:${e.size}@${e.start}`),c.push(`${this.getFileName(e.file_index)}`)})),r&&c.push("#EXT-X-ENDLIST");return c.join("\n")}async writeTrackText(t,e){const a=(0,path_1.resolve)(this.params.outputRoot,t),i=(0,path_1.resolve)(this.params.outputRoot,`${t}.gz`),s=(0,path_1.resolve)(this.params.outputRoot,`${t}.bin`);await(0,fs_extra_1.writeFile)(a,e,"utf-8"),await(0,utils_1.gzip)(a,i);if(await(0,utils_1.getFileSize)(i)>16){const t=(await(0,utils_2.getFileBufferSlice)(i,0,15)).toString("base64");await this.removeFirst16Bytes(i,s);return{...this.addOne({file_path:s,size:await(0,utils_1.getFileSize)(s)}),hash:t}}return{...this.addOne({file_path:a,size:await(0,utils_1.getFileSize)(a)}),hash:""}}async writeText(t,e){const a=(0,path_1.resolve)(this.params.outputRoot,t);await(0,fs_extra_1.writeFile)(a,e,"utf-8");return this.addOne({file_path:a,size:await(0,utils_1.getFileSize)(a)})}addOne({file_path:t,size:e,duration:a=0},i=!1){const{current_position:s,group_data:n,file_size_limit:r}=this;if(i&&n[this.current_index]||!(s+e<=r)){this.current_index+=1;const{current_index:i}=this;n[i]?n[i].files.push(t):n[i]={filename:this.getFileName(i),files:[t]};const s=0,r=e-1;return this.current_position=e,{file_index:i,start:s,end:r,duration:a,size:r-s+1}}{const{current_index:i}=this,r=s,o=s+e-1;return this.current_position=o+1,n[i]?n[i].files.push(t):n[i]={filename:this.getFileName(i),files:[t]},{file_index:i,start:r,end:o,duration:a,size:o-r+1}}}async safelyAddOne(t){const{current_position:e,file_size_limit:a}=this;return e+t.size<=a?0===e?(await this.insertSafeFile(),this.addOne(t)):this.addOne(t):(await this.insertSafeFile(),this.addOne(t))}async generateSafeFile(){const t=`safe_${this.safe_index++}.txt`,e=await this.getFilesize(),a=this.calculateMd5(e),i=`${a}${this.calculateMd5(a)}`,s=(0,path_1.resolve)(this.params.outputRoot,t);return await(0,fs_extra_1.writeFile)(s,i),{file_path:s,size:i.length}}async removeFirst16Bytes(t,e){await new Promise(((a,i)=>{const s=(0,fs_extra_1.createReadStream)(t,{start:16}),n=(0,fs_extra_1.createWriteStream)(e);s.pipe(n),n.on("finish",(()=>{a(!0)})),s.on("error",(t=>{i(t)})),n.on("error",(t=>{i(t)}))}))}calculateMd5(t){const e=(0,crypto_1.createHash)("md5");return e.update(`${t}`),e.digest("hex")}}exports.MediaGrouper=MediaGrouper;
@@ -1,11 +0,0 @@
1
- import { MediaBase } from './media-base';
2
- export declare class MediaText extends MediaBase {
3
- private builtin_tracks;
4
- private external_tracks;
5
- private get tracks();
6
- start(): Promise<void>;
7
- private parseBuiltinTextTracks;
8
- private parseBuiltinTextTrack;
9
- private parseExternalTextTracks;
10
- private parseExternalTextTrack;
11
- }
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.MediaText=void 0;const path_1=require("path"),media_base_1=require("./media-base"),utils_1=require("@soga/utils"),fs_extra_1=require("fs-extra"),constants_1=require("../constants"),main_1=require("../types/main");class MediaText extends media_base_1.MediaBase{builtin_tracks=0;external_tracks=0;get tracks(){return this.builtin_tracks+this.external_tracks}async start(){try{const t=constants_1.MEDIA_TEXT_DATA;if(await this.params.db.get(t))return;const a=await this.getMediainfo();this.builtin_tracks=a.text?.length??0,this.external_tracks=this.params.texts?.length??0;const e=await this.parseBuiltinTextTracks(),s=await this.parseExternalTextTracks(),i=[...e,...s];await this.params.db.put(constants_1.MEDIA_TEXT_DATA,i)}catch(t){await this.throwError({code:10500,message:t.message,stack:t.stack})}}async parseBuiltinTextTracks(){const t=(await this.getMediainfo()).text;if(!t?.length)return[];const a="parse_builtin_text_tracks",e=await this.params.db.get(a);if(e)return e;const s=t.length,i=[];for(let t=0;t<s;t++){const a=await this.parseBuiltinTextTrack(t);a&&i.push(a),await this.postProgress({type:main_1.ProcessStep.separate_text,percent:(t+1)/this.tracks})}return await this.params.db.put(a,i),i}async parseBuiltinTextTrack(t){const a=`parse_builtin_text_${t}`,e=await this.params.db.get(a);if(e)return e;if(0==e)return!1;const s=this.getTextName({position:"builtin",ext:"vtt",track_order:t}),i=(0,path_1.resolve)(this.params.outputRoot,s),r=["-i",this.params.filePath,"-vn","-an","-map",`0:s:${t}`,"-c","webvtt","-y",s];await this.ffmpeg(r,{cwd:this.params.outputRoot});if(await(0,utils_1.isValidFile)(i)){const{label:e,lang:r}=await(0,utils_1.getFileLanguage)(i),n={file:s,file_path:i,size:(await(0,fs_extra_1.stat)(i)).size,lang:r,title:e||`builtin_${t+1}`,builtin:!0,type:"vtt",source:null},o=this.getTextName({position:"builtin",ext:"ass",track_order:t}),l=(0,path_1.resolve)(this.params.outputRoot,o),p=["-i",this.params.filePath,"-vn","-an","-map",`0:s:${t}`,"-y",o];await this.ffmpeg(p,{cwd:this.params.outputRoot});if(await(0,utils_1.isValidFile)(l)){const t=await(0,fs_extra_1.stat)(l);n.source={file:o,file_path:l,size:t.size,type:"ass"}}return await this.params.db.put(a,n),n}return await this.params.db.put(a,!1),!1}async parseExternalTextTracks(){const{texts:t}=this.params;if(!t?.length)return[];const a="parse_external_text_tracks",e=await this.params.db.get(a);if(e)return e;const s=[],i=t.length;for(let a=0;a<i;a++){const e=await this.parseExternalTextTrack(a,t[a]);e&&s.push(e),await this.postProgress({type:main_1.ProcessStep.separate_text,percent:(this.builtin_tracks+(a+1))/this.tracks})}return await this.params.db.put(a,s),s}async parseExternalTextTrack(t,a){const e=`parse_external_text_${t}`,s=await this.params.db.get(e);if(s)return s;if(0==s)return!1;let i=a;if(!await(0,utils_1.isUtf8File)(a)){const e=`external_text_${t}${(0,path_1.parse)(a).ext}`;i=(0,path_1.resolve)(this.params.outputRoot,e),await(0,utils_1.saveFileAsUtf8)(a,i);if(!await(0,utils_1.isValidFile)(i))return!1}const r=this.getTextName({position:"external",ext:"vtt",track_order:t}),n=(0,path_1.resolve)(this.params.outputRoot,r),o=["-i",i,"-c","webvtt","-y",r];await this.ffmpeg(o,{cwd:this.params.outputRoot});const l=await(0,utils_1.isValidFile)(n),{label:p,lang:u}=await(0,utils_1.getFileLanguage)(n);if(l){const s={file:r,file_path:n,size:(await(0,fs_extra_1.stat)(n)).size,lang:u,title:p||`external_${t+1}`,builtin:!1,type:"vtt",source:null};if(!a.endsWith(".vtt"))if(a.endsWith(".srt")){const e=this.getTextName({position:"external",ext:"srt",track_order:t}),i=(0,path_1.resolve)(this.params.outputRoot,e);await(0,fs_extra_1.copy)(a,i);if(await(0,utils_1.isValidFile)(i)){const t=await(0,fs_extra_1.stat)(i);s.source={file:e,file_path:i,size:t.size,type:"srt"}}}else if(i.endsWith(".lrc")){const a=this.getTextName({position:"external",ext:"lrc",track_order:t}),e=(0,path_1.resolve)(this.params.outputRoot,a);await(0,fs_extra_1.copy)(i,e);const r=await(0,utils_1.getFileSize)(e);s.source={file:a,file_path:e,size:r,type:"lrc"}}else{const e=this.getTextName({position:"external",ext:"ass",track_order:t}),i=["-i",a,"-y",e];await this.ffmpeg(i,{cwd:this.params.outputRoot});const r=(0,path_1.resolve)(this.params.outputRoot,e);if(await(0,utils_1.isValidFile)(r)){const t=await(0,fs_extra_1.stat)(r);s.source={file:e,file_path:r,size:t.size,type:"ass"}}}return await this.params.db.put(e,s),s}return await this.params.db.put(e,!1),!1}}exports.MediaText=MediaText;
@@ -1,5 +0,0 @@
1
- import { MediaBase } from './media-base';
2
- export declare class VideoSeparator extends MediaBase {
3
- start(): Promise<void>;
4
- private separateVideo;
5
- }
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.VideoSeparator=void 0;const path_1=require("path"),media_base_1=require("./media-base"),utils_1=require("@soga/utils"),mediainfo_1=require("@soga/mediainfo"),main_1=require("../types/main");class VideoSeparator extends media_base_1.MediaBase{async start(){try{await this.separateVideo(),await this.postProgress({type:main_1.ProcessStep.separate_video,percent:1})}catch(e){await this.throwError({code:10800,message:e.message,stack:e.stack})}}async separateVideo(){const e="separate-video";if(await this.params.db.get(e))return;const t=await this.getMediainfo();if(!t?.video?.length)return;const a=t.video[0],i=this.getFormattedVideoName({width:a.width,height:a.height}),s=(0,path_1.resolve)(this.params.outputRoot,i),r=(0,mediainfo_1.isVideoSupport)(a.codec),o=["-sn","-an","-map","0:v:0"];let p="copy";r||(p=a.width*a.height>=2073600?"libx265":"libx264"),o.push("-c:v",p);const n=a.duration;o.push("-y"),await new Promise(((e,t)=>{const a=this.getFluent().input(this.params.filePath).outputOptions(o).on("progress",(async e=>{if(!r&&e.frames){const t=this.parseProgress(e,n);await this.postProgress({type:main_1.ProcessStep.separate_video,percent:t})}})).on("end",(async()=>{e("end"),a.kill("SIGKILL")})).on("error",(async e=>{t(e)})).output(s);a.run()}));if(!await(0,utils_1.isValidFile)(s))throw new Error("Separate Video track failed");await this.params.db.put(e,!0)}}exports.VideoSeparator=VideoSeparator;
@@ -1,28 +0,0 @@
1
- import { MediaBase } from './media-base';
2
- export declare class VideoThumbnailer extends MediaBase {
3
- private m3u8_data;
4
- private thumbnail_duration;
5
- private thumbnail_group;
6
- readonly row = 6;
7
- readonly col = 10;
8
- private screenshot_width;
9
- private screenshot_height;
10
- getScreenshotTempName(index: number): string;
11
- getScreenshotName(index: number): string;
12
- getTileName(index: number): string;
13
- getCoverName(): string;
14
- private updateImagePercent;
15
- start(): Promise<void>;
16
- init(): Promise<void>;
17
- private saveThumbnailInfo;
18
- private saveCoverInfo;
19
- private generateTiles;
20
- private generateOneTile;
21
- private createSpriteSheet;
22
- private getKeyFrameParams;
23
- private generateScreenshots;
24
- private initScreenshotSize;
25
- private initThumbnailGroup;
26
- initM3U8Data(): Promise<void>;
27
- private initThumbnailDuration;
28
- }
@@ -1 +0,0 @@
1
- "use strict";var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.VideoThumbnailer=void 0;const path_1=require("path"),media_base_1=require("./media-base"),utils_1=require("@soga/utils"),sharp_1=__importDefault(require("sharp")),fs_extra_1=require("fs-extra"),constants_1=require("../constants"),main_1=require("../types/main");class VideoThumbnailer extends media_base_1.MediaBase{m3u8_data;thumbnail_duration=30;thumbnail_group=[];row=6;col=10;screenshot_width=480;screenshot_height=270;getScreenshotTempName(t){return`video_screenshot_temp_${t}.png`}getScreenshotName(t){return`video_screenshot_${t}.png`}getTileName(t){return`video_tile_${t}.png`}getCoverName(){return"video_cover.png"}async updateImagePercent(t,e){const i="screenshot"==e?6*t/7:t/7+6/7;await this.postProgress({type:main_1.ProcessStep.transcode_thumbnail,percent:i})}async start(){try{const t=await this.getMediainfo();if(!t?.video?.length)return;if(await this.init(),!this.m3u8_data)return;await this.generateScreenshots(),await this.generateTiles(),await this.saveThumbnailInfo(),await this.saveCoverInfo()}catch(t){await this.throwError({code:11e3,message:t.message,stack:t.stack})}}async init(){await this.initScreenshotSize(),await this.initM3U8Data(),await this.initThumbnailDuration(),await this.initThumbnailGroup()}async saveThumbnailInfo(){const t=constants_1.VIDEO_THUMBNAIL_DATA;if(await this.params.db.get(t))return;const{col:e,row:i,thumbnail_group:s,screenshot_width:a,screenshot_height:h}=this,r=s.length,n=[],o=[];for(let t=0;t<r;t++){const r=s[t],c=r.start_time,u=r.end_time,m=Math.floor(t/(i*e)),d=t%i*a,l=Math.floor(t%(e*i)/i)*h,_=this.getTileName(m);o.push(_),n.push({file:_,st:c,et:u,x:d,y:l,w:a,h:h})}const c=[...new Set(o)],u=c.length,m=[];for(let t=0;t<u;t++){const e=c[t],i=(0,path_1.resolve)(this.params.outputRoot,e),s=await(0,utils_1.getFileSize)(i);m.push({file:e,file_path:i,size:s})}const d={width:this.screenshot_width,height:this.screenshot_height,row:this.row,col:this.col,tile_list:m,sprite_list:n};await this.params.db.put(t,d)}async saveCoverInfo(){const t=constants_1.VIDEO_COVER_DATA;if(await this.params.db.get(t))return;const e=await this.params.db.get("screenshot_max"),i=e?.index||0,{screenshot_width:s,screenshot_height:a}=this,h=this.getScreenshotName(i),r=(0,path_1.resolve)(this.params.outputRoot,h),n=this.getCoverName(),o=(0,path_1.resolve)(this.params.outputRoot,n);await(0,fs_extra_1.copy)(r,o);const c={cover_name:n,cover_path:o,size:await(0,utils_1.getFileSize)(o),width:s,height:a};await this.params.db.put(t,c)}async generateTiles(){const t=await this.params.db.get("generate_tiles_index")||0,{length:e}=this.thumbnail_group,i=this.col*this.row,s=Math.ceil(e/i);for(let e=t;e<s;e++)await this.generateOneTile(e),await this.updateImagePercent((e+1)/s,"tile")}async generateOneTile(t){const{col:e,row:i}=this,s=this.thumbnail_group.length,a=i*e,h=t*a,r=Math.min(a,s-h),n=[];for(let t=0;t<r;t++){const e=this.getScreenshotName(h+t),i=(0,path_1.resolve)(this.params.outputRoot,e);n.push(i)}const o=this.getTileName(t),c=(0,path_1.resolve)(this.params.outputRoot,o);await this.createSpriteSheet(n,c),await this.params.db.put("generate_tiles_index",t+1)}async createSpriteSheet(t,e){const{screenshot_width:i,screenshot_height:s}=this,a=Math.ceil(t.length/this.row),h=i*Math.min(this.row,t.length),r=s*a,n=[];for(let e=0;e<this.row*this.col;e++){const a=Math.floor(e/this.row),h=e%this.row*i,r=a*s;if(e<t.length){const a=await(0,sharp_1.default)(t[e]).resize(i,s).toBuffer();n.push({input:a,left:h,top:r})}}const o=(0,sharp_1.default)({create:{width:h,height:r,channels:4,background:"white"}}).png();await o.composite(n).toFile(e)}getKeyFrameParams(t,e=0){if(t<1)return["-vframes","1"];return["-ss",`${Math.max(t-e-.3,.1).toFixed(3)}`,"-vframes","1"]}async generateScreenshots(){const t="generate_screenshots";if(await this.params.db.get(t))return;const e="screenshot_index",i=await this.params.db.get(e)||0,s="screenshot_max",a=await this.params.db.get(s),h={index:0,size:0};if(a){const t=a;h.index=t.index,h.size=t.size}const{segments:r}=this.m3u8_data,{thumbnail_group:n,screenshot_width:o}=this,{length:c}=n,u=async(t,{errorTimes:i,fixedSeconds:a})=>{const c=n[t].item.index,m=r[c];try{const i=this.getScreenshotTempName(t),r=`concat:${m.init_uri}|${m.uri}`,n=this.getKeyFrameParams(m.duration,a),c=o>this.screenshot_height?`${o}:-2`:`-2:${this.screenshot_height}`,u=["-i",r,...n,"-vf",`scale=${c}:flags=lanczos`,"-y",i];await this.ffmpeg(u,{cwd:this.params.outputRoot});const d=(0,path_1.resolve)(this.params.outputRoot,i),l=this.getScreenshotName(t),_=(0,path_1.resolve)(this.params.outputRoot,l);await(0,sharp_1.default)(d).png({quality:75}).toFile(_);const p=await(0,utils_1.getFileSize)(_);if(0===p)throw new Error(`Generate video thumbnail error: [index: ${t}, start_time: ${m.start_time}, end_time: ${m.end_time}]`);p>=h.size&&(h.index=t,h.size=p,await this.params.db.put(s,h)),await(0,fs_extra_1.remove)(d),await this.params.db.put(e,t+1)}catch(e){const s=Math.max(10,m.duration);if(!(i<3))throw e;await u(t,{errorTimes:i+1,fixedSeconds:a+s/5})}};for(let t=i;t<c;t++)await u(t,{errorTimes:0,fixedSeconds:0}),await this.updateImagePercent((t+1)/c,"screenshot");await this.params.db.put(t,!0)}async initScreenshotSize(){const t="screenshot_size",e=await this.params.db.get(t);if(e)return this.screenshot_width=e.width,void(this.screenshot_height=e.height);const i=(await this.getMediainfo()).video[0],{width:s,height:a}=i,h=s>a?480:270;this.screenshot_width=2*Math.floor(Math.min(h,s)/2),this.screenshot_height=2*Math.floor(this.screenshot_width*(a/s)/2),await this.params.db.put(t,{width:this.screenshot_width,height:this.screenshot_height})}async initThumbnailGroup(){const t="thumbnail_group",e=await this.params.db.get(t);if(e)return void(this.thumbnail_group=e);const i=[],{segments:s}=this.m3u8_data,{length:a}=s,h=this.thumbnail_duration;let r=0,n=[];for(let t=0;t<a;t++){const e=s[t];if(r+=e.duration,n.push(e),r>=h||t===a-1){const t=n.length,e=n[0],s=n[t-1];i.push({duration:r,start_time:e.start_time,end_time:s.end_time,items:n,item:s}),r=0,n=[]}}if(i.length>1){if(i[i.length-1].duration<1){const t=i.pop(),e=i[i.length-2];e.duration=e.duration+t.duration,e.end_time=t.end_time,e.items=e.items.concat(t.items),e.item=t.item,t.items}}i.forEach((t=>{const{items:e}=t;for(let i=e.length-1;i>=0;i--)if(e[i].duration>1){t.item=e[i];break}})),this.thumbnail_group=i,await this.params.db.put(t,i)}async initM3U8Data(){const t=await this.getVideoM3U8Info();this.m3u8_data=t.data}async initThumbnailDuration(){let t=30;const e="thumbnail_duration4",i=await this.params.db.get(e);if(i)return this.thumbnail_duration=i,this.thumbnail_duration;const s=await this.getMediainfo(),a=s?.video;if(!a?.length)return t;const h=a[0],r=h.duration||s?.general?.duration,{width:n,height:o}=h;n*o>=518400&&(t=10),n*o>=921600&&(t=20),n*o>=2073600&&(t=30),n*o>=3686400&&(t=40),n*o>=8294400&&(t=60);const c=r??0;if(c&&(c<60&&(t=0),c<300&&(t=Math.ceil(t/4)),c<600&&(t=Math.ceil(t/2)),c>3600)){const e=c/3600;t=Math.ceil(t*e)}return t=Math.min(60,t),this.thumbnail_duration=t,await this.params.db.put(e,t),t}}exports.VideoThumbnailer=VideoThumbnailer;
@@ -1,6 +0,0 @@
1
- import { MediaBase } from './media-base';
2
- export declare class VideoTranscoder extends MediaBase {
3
- start(): Promise<void>;
4
- private saveData;
5
- private transcodeVideo;
6
- }
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.VideoTranscoder=void 0;const path_1=require("path"),media_base_1=require("./media-base"),constants_1=require("../constants"),mediainfo_1=require("@soga/mediainfo"),main_1=require("../types/main"),utils_1=require("@soga/utils");class VideoTranscoder extends media_base_1.MediaBase{async start(){try{await this.transcodeVideo(),await this.saveData(),await this.postProgress({type:main_1.ProcessStep.transcode_video,percent:1})}catch(t){await this.throwError({code:10900,message:t.message,stack:t.stack})}}async saveData(){const t=await this.getMediainfo();if(!t.video?.length)return;const e=constants_1.VIDEO_TRANSCODER_DATA;if(await this.params.db.get(e))return;const{width:a,height:i}=t.video[0],s=this.getFormattedVideoName({width:a,height:i}),o=(0,path_1.resolve)(this.params.outputRoot,s),d=this.getVideoManifestName({width:a,height:i}),r=(0,path_1.resolve)(this.params.outputRoot,d),{data:h}=await(0,mediainfo_1.parseM3U8)(r),{videos:n}=await(0,mediainfo_1.getMp4boxInfo)(o),{codec:c}=n[0],{bandwidth:m,average_bandwidth:p}=this.calculateBandwidth(h),_=(await(0,mediainfo_1.parseMediaInfo)(o)).video[0],w=(0,mediainfo_1.getStandardInfo)(a,i),g={m3u8:h,m3u8_path:r,codec:c,width:a,height:i,bandwidth:m,average_bandwidth:p,bitdepth:_?.bitdepth,bitrate:_?.bitrate,codec_name:_?.codec,framerate:_?.framerate,label:w.label};await this.params.db.put(e,g)}async transcodeVideo(){const t="transcode-video";if(await this.params.db.get(t))return;const e=(await this.getMediainfo()).video;if(!e?.length)return;const a=e[0],i={width:a.width,height:a.height},s=this.getFormattedVideoName(i),o=this.getVideoInitName(i),d=this.getVideoManifestName({width:a.width,height:a.height}),r=["-i",s,"-an","-sn","-c:v","copy","-hls_list_size","0","-hls_time","6","-hls_segment_type","fmp4","-hls_fmp4_init_filename",o,"-y",d];await this.ffmpeg(r,{cwd:this.params.outputRoot});const h=(0,path_1.resolve)(this.params.outputRoot,d);if(!await(0,utils_1.isValidFile)(h))throw new Error("Transcode video track failed");await this.params.db.put(t,!0)}}exports.VideoTranscoder=VideoTranscoder;
@@ -1,13 +0,0 @@
1
- import { type PrepareResult } from '../types/main';
2
- import { Common } from '../common/common';
3
- export declare class Prepare extends Common {
4
- get isVideo(): boolean;
5
- get isAudio(): boolean;
6
- get isMedia(): boolean;
7
- get isTxt(): boolean;
8
- get isImg(): boolean;
9
- start(): Promise<PrepareResult>;
10
- getResult(): Promise<PrepareResult>;
11
- private initProcess;
12
- private getMediainfo;
13
- }
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Prepare=void 0;const types_1=require("@soga/types"),main_1=require("../types/main"),mediainfo_1=require("@soga/mediainfo"),utils_1=require("@soga/utils"),common_1=require("../common/common"),constants_1=require("../constants");class Prepare extends common_1.Common{get isVideo(){return this.params.type==types_1.RecordType.VIDEO}get isAudio(){return this.params.type==types_1.RecordType.AUDIO}get isMedia(){return this.isAudio||this.isVideo}get isTxt(){return this.params.type==types_1.RecordType.TEXT&&this.params.ftype==types_1.RecordFtype.TEXT_TXT}get isImg(){return this.params.type==types_1.RecordType.IMAGE}async start(){try{return await this.initProcess()}catch(e){await this.throwError({code:10100,message:e.message,stack:e.stack})}}async getResult(){return await this.initProcess()}async initProcess(){const e=constants_1.PREPARE_RESULT,t=await this.params.db.get(e);if(t)return t;const a={[main_1.ProcessStep.prepare]:{weight:0,percent:1},[main_1.ProcessStep.separate_video]:{weight:0,percent:0},[main_1.ProcessStep.separate_audio]:{weight:0,percent:0},[main_1.ProcessStep.separate_text]:{weight:0,percent:0},[main_1.ProcessStep.transcode_video]:{weight:0,percent:0},[main_1.ProcessStep.transcode_audio]:{weight:0,percent:0},[main_1.ProcessStep.transcode_thumbnail]:{weight:0,percent:0},[main_1.ProcessStep.transcode_source]:{weight:0,percent:0},[main_1.ProcessStep.transcode_txt]:{weight:0,percent:0},[main_1.ProcessStep.transcode_img]:{weight:0,percent:0},[main_1.ProcessStep.group_media]:{weight:0,percent:0},[main_1.ProcessStep.upload_baidu]:{weight:0,percent:0},[main_1.ProcessStep.upload_ali]:{weight:0,percent:0},[main_1.ProcessStep.end]:{weight:0,percent:0}},s={keep_preview:this.params.keepPreview,keep_source:this.params.keepSource},i=await(0,utils_1.getFileSize)(this.params.filePath);a[main_1.ProcessStep.prepare].weight=a[main_1.ProcessStep.end].weight=Math.ceil(i/1024/1024/100);let r=0;if(this.isMedia)if(this.params.keepPreview){const e=await this.getMediainfo(),t=e?.general?.duration??0;if(e.video?.length){const t=e.video[0],{width:s,height:i,codec:r}=t,o=(0,mediainfo_1.isVideoSupport)(r),{duration:n}=e.general,p=o?n/400:n*(s*i)/1920/1080;a[main_1.ProcessStep.separate_video].weight=Math.ceil(p),a[main_1.ProcessStep.transcode_video].weight=Math.ceil(.01*n)}if(e.audio?.length){const{duration:t}=e.general;let s=0;e.audio.forEach((e=>{const{codec:a,lossless:i,channels:r}=e,o={high_need:!1,high_transcode:!1,adapt_need:!1,adapt_transcode:!1};(0,mediainfo_1.isAudioAdapt)(a)?r>2?(o.high_need=!0,o.high_transcode=!1,o.adapt_need=!0,o.adapt_transcode=!0):(o.high_need=!1,o.adapt_need=!0,o.adapt_transcode=!1):(0,mediainfo_1.isAudioHigh)(a)?(o.high_need=!0,o.high_transcode=!1,o.adapt_need=!0,o.adapt_transcode=!0):(o.high_need=!1,o.adapt_need=!0,o.adapt_transcode=!1,i&&(o.high_need=!0,o.high_transcode=!0)),o.adapt_need&&(s+=o.adapt_transcode?t/40:5),o.high_need&&(s+=o.high_transcode?t/20:10)})),a[main_1.ProcessStep.separate_audio].weight=Math.ceil(s),a[main_1.ProcessStep.transcode_audio].weight=Math.ceil(.003*t)}const o=e.video?.length??0,n=e.audio?.length??0;if(o||n){s.keep_preview=!0;const o=(e.text?.length||0)+(this.params.texts?.length||0);a[main_1.ProcessStep.separate_text].weight=Math.ceil(o),a[main_1.ProcessStep.transcode_thumbnail].weight=Math.ceil(.08*t),r+=i,a[main_1.ProcessStep.group_media].weight=Math.ceil(i/1024/1024/50)}else s.keep_preview=!1,s.keep_source=!0,r+=i}else s.keep_source=!0,r+=i;else this.isTxt?s.keep_preview?(a[main_1.ProcessStep.transcode_txt].weight=Math.ceil(i/1024/1024/1),r+=i):(s.keep_source=!0,r+=i):this.isImg?s.keep_preview&&(a[main_1.ProcessStep.transcode_img].weight=Math.ceil(i/1024/1024/1),r+=i):(s.keep_source=!0,r+=i);s.keep_source&&(a[main_1.ProcessStep.transcode_source].weight=Math.ceil(i/1024/1024/50)),this.params.hosts.includes(types_1.HostType.BAIDU)&&(a[main_1.ProcessStep.upload_baidu].weight=Math.ceil(r/1024/1024/1)),this.params.hosts.includes(types_1.HostType.ALI)&&(a[main_1.ProcessStep.upload_ali].weight=Math.ceil(r/1024/1024/2));const o={options:s,data:a};return await this.params.db.put(e,o),await this.postMessage({id:this.params.id,type:"prepare",data:o}),o}async getMediainfo(){const e="adapt_mediainfo",t=await this.params.db.get(e);if(t)return t;const a=await(0,mediainfo_1.parseMediaInfo)(this.params.filePath);return await this.params.db.put(e,a),a}}exports.Prepare=Prepare;
@@ -1,10 +0,0 @@
1
- import { Common } from '../common/common';
2
- import { SourceGroupData } from '../types/main';
3
- export declare class SourceEncoder extends Common {
4
- private readonly head_size;
5
- private getZipFilePath;
6
- start(): Promise<SourceGroupData>;
7
- gzip(): Promise<void>;
8
- getResult(): Promise<SourceGroupData>;
9
- private getSplitInfo;
10
- }
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.SourceEncoder=void 0;const path_1=require("path"),utils_1=require("@soga/utils"),utils_2=require("@soga/utils"),types_1=require("@soga/types"),common_1=require("../common/common"),main_1=require("../types/main");class SourceEncoder extends common_1.Common{head_size=32;getZipFilePath(){return(0,path_1.resolve)(this.params.outputRoot,"zip.bin")}async start(){try{await this.gzip(),await this.postProgress({type:main_1.ProcessStep.transcode_source,percent:1});return await this.getResult()}catch(t){await this.throwError({code:10200,message:t.message,stack:t.stack})}}async gzip(){const t="source_encoder_gzip";if(await this.params.db.get(t))return;const e=this.params.filePath,s=this.getZipFilePath();await(0,utils_1.gzip)(e,s,(async({percent:t})=>{await this.postProgress({type:main_1.ProcessStep.transcode_source,percent:t})})),await this.params.db.put(t,!0)}async getResult(){const t="source_encode_result",e=await this.params.db.get(t);if(e)return e;const s=this.getZipFilePath(),a=await(0,utils_1.getFileSize)(s),i=await this.getSplitInfo(),r=(await(0,utils_2.getFileBufferSlice)(i.head.path,i.head.start,i.head.end)).toString("base64"),{parts:o}=i,n={size:a-Math.min(this.head_size,i.head.end+1),head:r,parts:o};return await this.params.db.put(t,n),n}async getSplitInfo(){const t=this.getZipFilePath(),e=await(0,utils_1.getFileSize)(t),s={head:{},parts:[]},{head_size:a,file_size_limit:i}=this;if(e<=a)s.head={start:0,end:e-1,path:t};else{let r=0;s.head={start:0,end:a-1,path:t};for(let o=a;o<=e-1;o+=i){const a=Math.min(o+i-1,e-1),n={start:o,end:a,size:a-o+1,path:t,index:r,file:`source_${r}`,md5:await(0,utils_2.calculateMd5)({file:t,start:o,end:a})};if(this.params.hosts.includes(types_1.HostType.BAIDU)){const e=await(0,utils_2.calculateMd4)({file:t,start:o,end:a});n.md4=e}if(this.params.hosts.includes(types_1.HostType.ALI)){const e=await(0,utils_2.calculateSha1)({file:t,start:o,end:a});n.sha1=e}s.parts.push(n),r++}}return s}}exports.SourceEncoder=SourceEncoder;
@@ -1,20 +0,0 @@
1
- import { Common } from '../common/common';
2
- import { TxtGroupData } from '../types/main';
3
- export declare class TxtEncoder extends Common {
4
- private readonly content_size_1;
5
- private readonly content_size_2;
6
- private totalPage;
7
- private currentFileIndex;
8
- private readonly hashBuffer;
9
- private get hashBufferLength();
10
- private groupedInfo;
11
- private getU8FileName;
12
- private getU8FilePath;
13
- private getGzFileName;
14
- private getGzFilePath;
15
- start(): Promise<TxtGroupData>;
16
- private getTxtResult;
17
- private txtConvertToUtf8File;
18
- txtConvertToGzipFile(): Promise<string | number | true | object>;
19
- private calculateSlice;
20
- }
@@ -1 +0,0 @@
1
- "use strict";var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.TxtEncoder=void 0;const path_1=require("path"),fs_extra_1=require("fs-extra"),utils_1=require("@soga/utils"),zlib_1=__importDefault(require("zlib")),utils_2=require("@soga/utils"),common_1=require("../common/common"),main_1=require("../types/main"),types_1=require("@soga/types");class TxtEncoder extends common_1.Common{content_size_1=30720;content_size_2=51200;totalPage=1;currentFileIndex=0;hashBuffer=Buffer.from("dpan");get hashBufferLength(){return this.hashBuffer.length}groupedInfo={};getU8FileName(){return`text_utf8_${this.params.id}.txt`}getU8FilePath(){return(0,path_1.resolve)(this.params.outputRoot,this.getU8FileName())}getGzFileName(t){return`preview_${t}`}getGzFilePath(t){return(0,path_1.resolve)(this.params.outputRoot,this.getGzFileName(t))}async start(){try{await this.txtConvertToUtf8File(),await this.txtConvertToGzipFile();return await this.getTxtResult()}catch(t){await this.throwError({code:10300,message:t.message,stack:t.stack})}}async getTxtResult(){const t=this.currentFileIndex,e={pad:this.hashBufferLength,pages:this.totalPage,map:{start:this.groupedInfo.map_start,end:this.groupedInfo.map_end,file:this.groupedInfo.map_file},parts:[]};for(let i=0;i<t+1;i++){const t=this.getGzFileName(i),s=this.getGzFilePath(i),a=await(0,utils_2.getFileSize)(s),r={start:0,end:a-1,size:a,path:s,index:i,file:t,md5:await(0,utils_1.calculateMd5)({file:s,start:0,end:a-1})};if(this.params.hosts.includes(types_1.HostType.BAIDU)){const t=await(0,utils_1.calculateMd4)({file:s,start:0,end:a-1});r.md4=t}if(this.params.hosts.includes(types_1.HostType.ALI)){const t=await(0,utils_1.calculateSha1)({file:s,start:0,end:a-1});r.sha1=t}e.parts.push(r)}return e}async txtConvertToUtf8File(){const t="txt_encoder_to_utf8";if(await this.params.db.get(t))return;const e=this.params.filePath,i=this.getU8FilePath();await(0,utils_2.saveFileAsUtf8)(e,i),await this.params.db.put(t,!0),await this.postProgress({type:main_1.ProcessStep.transcode_txt,percent:.2})}async txtConvertToGzipFile(){const t="txt_encoder_to_gzip",e=await this.params.db.get(t);if(e)return this.groupedInfo=e,e;const i=this.getU8FilePath(),s=await this.calculateSlice(),{length:a}=s,r={};let n=0,o=(0,fs_extra_1.createWriteStream)(this.getGzFilePath(this.currentFileIndex));for(let t=0;t<a;t++){const e=t==a-1?0:t+1,h=s[e],{index:l,start:u,end:c}=h,f={page:l+1,text:(await(0,utils_1.getFileBufferSlice)(i,u,c)).toString("utf8")},p=JSON.stringify(f);let _=Buffer.from(p,"utf8");if(0==e){const t={...f,map:r};_=Buffer.from(JSON.stringify(t),"utf8")}await new Promise(((t,i)=>{zlib_1.default.gzip(_,((s,a)=>{s&&i(s);const h=a.length<this.hashBufferLength?this.hashBuffer:a.subarray(0,this.hashBufferLength),l=Buffer.concat([h,a]),u=l.length;n+u>this.file_size_limit&&(o.end(),this.currentFileIndex++,o=(0,fs_extra_1.createWriteStream)(this.getGzFilePath(this.currentFileIndex)),n=0);const c=n,f=n+u-1;o.write(l),n+=u,0==e?(this.groupedInfo={total_page:this.totalPage,map_start:c,map_end:f,map_file:this.getGzFileName(this.currentFileIndex)},o.end()):r[`p_${e+1}`]={f:this.getGzFileName(this.currentFileIndex),s:c,e:f},t(!0)}))}))}const h=this.groupedInfo;await this.params.db.put(t,h);try{this.params.debug||await(0,fs_extra_1.remove)(this.getU8FilePath())}catch(t){}return await this.postProgress({type:main_1.ProcessStep.transcode_txt,percent:1}),h}async calculateSlice(){const t=this.getU8FilePath();return await new Promise(((e,i)=>{(0,fs_extra_1.readFile)(t,"utf8",((t,s)=>{if(t)return void i(t);const a=this.content_size_1,r=this.content_size_2,n=[];let o=0,h=0,l=0;for(;h<s.length;){let t=Math.min(h+r,s.length);const e=Math.min(h+a,s.length),i=s.slice(e,t).indexOf("#");-1!==i&&(t=e+i);const u=s.slice(h,t),c=Buffer.byteLength(u);n.push({index:o++,start:l,end:l+c}),this.totalPage=o,h=t,l+=c}e(n)}))}))}}exports.TxtEncoder=TxtEncoder;
@@ -1,102 +0,0 @@
1
- import { RecordType, RecordFtype, HostType } from '@soga/types';
2
- import { Level } from 'level';
3
- import type { M3U8Manifest } from '@soga/mediainfo';
4
- export type Params = {
5
- uid: number;
6
- id: number;
7
- filePath: string;
8
- type: RecordType;
9
- ftype: RecordFtype;
10
- ffmpegPath: string;
11
- keepPreview: boolean;
12
- keepSource: boolean;
13
- outputRoot: string;
14
- hosts: HostType[];
15
- maxPartSize?: number;
16
- texts?: string[];
17
- debug?: boolean;
18
- };
19
- export type EncoderParams = Params & {
20
- db: Level<string, string | boolean | number | object>;
21
- };
22
- export type PrepareParams = EncoderParams;
23
- export type AudioConfig = {
24
- need: boolean;
25
- codec: string;
26
- channels: number;
27
- };
28
- export type AudioTrackQualityInfo = {
29
- codec: string;
30
- codec_name: string;
31
- lossless: boolean;
32
- channels: number;
33
- language: string;
34
- order: number;
35
- size: number;
36
- m3u8: M3U8Manifest;
37
- m3u8_path: string;
38
- bandwidth: number;
39
- average_bandwidth: number;
40
- };
41
- export type AudioTrackItem = {
42
- high?: AudioTrackQualityInfo;
43
- adapt: AudioTrackQualityInfo;
44
- };
45
- export type VideoTrackQualityInfo = {
46
- codec: string;
47
- codec_name: string;
48
- width: number;
49
- height: number;
50
- bandwidth: number;
51
- average_bandwidth: number;
52
- bitdepth?: number;
53
- bitrate?: number;
54
- framerate?: number;
55
- label: string;
56
- m3u8: M3U8Manifest;
57
- m3u8_path: string;
58
- };
59
- export type VideoTrackItem = VideoTrackQualityInfo;
60
- export type VideoThumbnailInfo = {
61
- row: number;
62
- col: number;
63
- width: number;
64
- height: number;
65
- tile_list: {
66
- file: string;
67
- file_path: string;
68
- size: number;
69
- }[];
70
- sprite_list: {
71
- file: string;
72
- st: number;
73
- et: number;
74
- x: number;
75
- y: number;
76
- w: number;
77
- h: number;
78
- }[];
79
- };
80
- export type MediaCoverInfo = {
81
- cover_name: string;
82
- cover_path: string;
83
- size: number;
84
- width: number;
85
- height: number;
86
- };
87
- export type MediaTextType = 'srt' | 'vtt' | 'ass' | 'lrc';
88
- export type VideoSubtitleInfo = {
89
- file: string;
90
- file_path: string;
91
- size: number;
92
- lang: string;
93
- title: string;
94
- builtin: boolean;
95
- type: MediaTextType;
96
- source: {
97
- file: string;
98
- file_path: string;
99
- size: number;
100
- type: MediaTextType;
101
- } | null;
102
- };
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});
@@ -1,2 +0,0 @@
1
- import { Level } from 'level';
2
- export declare const getEncoderLevel: (outputRoot: string, input: string) => Promise<Level<string, string | number | boolean | object>>;
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getEncoderLevel=void 0;const level_1=require("level"),path_1=require("path"),utils_1=require("@soga/utils"),fs_extra_1=require("fs-extra"),getEncoderLevel=async(e,t)=>{const r=(0,path_1.resolve)(e,"encoder_store"),i=new level_1.Level(r,{valueEncoding:"json"});if(!await(0,fs_extra_1.exists)(t))return i;const a=await(0,utils_1.getFileSize)(t),s="__size__",l="__md4__",o=await i.get(s);if(!o){const e=await(0,utils_1.calculateMd4)({file:t,start:0,end:a-1});return await i.put(l,e),await i.put(s,a),i}if(o==a){if(await i.get(l)==await(0,utils_1.calculateMd4)({file:t,start:0,end:a-1}))return i}throw new Error("Conflict with last processed file")};exports.getEncoderLevel=getEncoderLevel;