1.0.2 • Published 2 months ago

m3u8-conver v1.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

English|简体中文

📖Introduction

Convert network or local m3u8 files to media files (such as mp4, avi, etc.).

  • Decode AES-123-CBC
  • Decode AES-192-CBC
  • Decode AES-256-CBC
  • parser m3u8 localfile
  • custom save format.ext
  • concurrence download
  • custom m3u8 parser
  • request optons

🚀Install

Make sureNodejs>=v16.13.0

If you do not install Nodejs, Please install Nodejs

# npm
npm i m3u8-conver -g

# pnpm
pnpm i m3u8-conver -g

🚗Use

1. Used on the command

# help
mconver -h
# Converts "https://www.test.com/test.m3u8" to the current directory "output. Mp4"
mconver -i "https://www.test.com/test.m3u8"
# or parser local file, and set output file, and set the number of concurrent downloads
mconver -i "./test.m3u8" -o "./output.mp4" -c 10

2. Used in the code

custom parser see Custom-parser

import mconver from "m3u8-conver"
// basic
const output = await mconver({
    input: "https://www.test.com",
})
console.log("convered path: ", output)

// other options
// More configuration see document #Options
await mconver({
    input: "https://www.test.com",
    name: "output.mp4",
    concurrency: 6,
    requestOptions: {
        method: "GET",
        headers: {
            "x-token": "your token",
            Cookie: "your cookie",
        },
    },
    parsered(fragments) {
        console.log(fragments, "m3u8 parsered!")
    },
    onchange(total, current, fragment) {
        console.log(`downloading... [${current + 1}/${total}]\r`)
    },
    parser(fragment, index) {
        // custom parser ...
        return fragment
    },
})

🔧Options

options

  • *inputString: indicates the url or local file of the m3u8 file to be converted
  • concurrencyNumber: concurrency max number, default: 1
  • pathString: save path after conversion. Default: Execution root path. process.cwd()
  • nameString: indicates the converted file name (including the suffix). Default: "execute timestamp.mp4". new Date().getTime() + ".mp4"
  • tempDirString: indicates the temporary save path for ts chips. Default: m3u8-converproject root path. path.resolve(__dirname, "../", ".temp"),
  • encodeSuffixString: indicates the suffix of an undecrypted ts slice. The default is ".encode".
  • decodeSuffixString: decrypted or undecrypted ts slice suffix. Default: ".ts"
  • clearBoolean: Specifies whether to execute only the clear cache, default: false.
  • requestOptionshttpOption: https request options, default: {}. The following are common configurations. See more detailsgot-options.

  • parsered(fragments)Function: A callback function that is triggered when the solution is complete

    • fragmentsArray: indicates information about all fragments after resolution
  • onchange(total, current, fragment)Function: A callback function that is triggered when a fragment is downloaded

    • totalNumber: indicates the fragment total number
    • currentNumber: indicates the current index
    • fragmentObject: indicates information about the current ts fragment
  • downloaded()Function: Callback after downloading all fragment information

  • parser(fragment, index)Function: custom parser, When this parameter is used, the internal parser will not execute, see Custom parser(#Custom parser)

    • fragmentObject: indicates information about the current ts fragment
    • indexNumber: indicates the current index

✏️Advanced

Custom-parser

In the vast majority of cases, you only need to use the standard m3u8 parser we provide without knowing its internal implementation.

In order to provide a more flexible way to use, we provide custom parsers to meet different needs.

Parser parameters

The parser must be a function whose 'this' points to an instance of Origin, and if you use an arrow function, then you won't be able to access its internal properties (if you don't need them, you can ignore them). It takes two arguments' fragment 'and' index '(the index of the currently executed fragment).

fragment

  • fragmentObject: indicates all parsed ts fragments
    • durationNumber: indicates the duration of the segment
    • uriString: uri link of the fragment
    • keyObject: indicates an encryption parameter. If no, it indicates that no encryption is performed
      • key.methodString: indicates the encryption method
      • key.uriString: uri link of the encrypted key
      • key.ivArrayBuffer: indicates the encrypted iv
      • key.keyArrayBuffer: indicates the encrypted key content. The value is obtained by the uri
    • encryptionBoolean: Specifies whether the segment is encrypted. The default value is false
    • timelineNumber: Timeline

Parser return value

The parser must return a fragment object, and if there is an encryption argument (fragment.key), it must ensure its correctness.

Because when you use a custom parser, our parser will not execute, and the returned fragment will be used as the basis for subsequent decryption and download of ts fragment. If the encryption parameter of 'fragment' returned is incorrect, the m3u8 file cannot be successfully converted. If there is no encryption parameter, ignore it.

Example

The following example is a partial implementation of our parser, which you can use as a reference to implement your own parser.

import mconver from "m3u8-conver"
import got from "got" // or use other network request tools
import url from "url"
import { detectAesMode, isWebLink } from "m3u8-conver/dist/utilities.js"

await mconver({
    url: "https://www.test.com",
    parser
})

async function parser(fragment, index) {
    console.log("useing custom parser!")
    const uriIsWebLink = isWebLink(fragment.uri);
    if (this.model === 'Local' && !uriIsWebLink) {
        throw new Error("The download link is missing the host, please try using url mode!");
    }
    // fragment uri has portcol?
    fragment.uri = uriIsWebLink ? fragment.uri : url.resolve(this.options.input, fragment.uri);
    // if fragment not key, this not's encryption
    const key = Object.assign({}, fragment.key);
    if (!key || Object.keys(key).length === 0) {
        return fragment;
    }
    if (!key.uri || !key.iv) {
        throw new Error("The fragment encryption key or iv is missing the download link!");
    }
    // next all encryption is true
    key.uri = isWebLink(key.uri) ? key.uri : url.resolve(fragment.uri, key.uri);
    if (this.cache.get(key.uri)) {
        // Using the key to identify the real encryption mode
        // be confined to AES-128-CBC | AES-192-CBC | AES-256-CBC, default AES-128-CBC
        key.key = this.cache.get(key.uri);
    } else {
        const keyResponse = await got(key.uri, { ...this.options.requestOptions, responseType: "buffer" });
        const keyBuffer = Buffer.isBuffer(keyResponse.body.buffer) ? keyResponse.body.buffer : Buffer.from(keyResponse.body.buffer);
        this.cache.set(key.uri, keyBuffer);
        key.key = keyBuffer;
    }
    if (key.key) {
        key.method = detectAesMode(key.key);
    } else {
        key.method = "AES-128-CBC";
    }
    // reset fragment.key
    fragment.key = key;
    return fragment;
}