Skip to content

Commit

Permalink
Merge pull request #5 from vitalygashkov/next
Browse files Browse the repository at this point in the history
Added Russian README, disabled init data fetching when parse segment base, sort tracks by bandwidth, added filtering by codecs, added separate filter functions
  • Loading branch information
vitalygashkov authored May 5, 2024
2 parents b80eb6d + a643ea7 commit bc9218e
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 62 deletions.
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: npm
directory: '/'
schedule:
interval: daily
open-pull-requests-limit: 10
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: ci

on:
push:
paths-ignore:
- 'docs/**'
- '*.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20.x

- name: Install
run: |
npm install
- name: Lint
run: |
npm run lint
- name: Run tests
run: |
npm run test
41 changes: 0 additions & 41 deletions .github/workflows/release.yml

This file was deleted.

13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

Library for parsing MPEG-DASH and HLS manifests. Made with the purpose of obtaining a simplified representation convenient for further downloading of segments.

<div align="left">
<span>English</span> •
<a href="https://github.com/vitalygashkov/dasha/tree/main/docs/README.ru.md">Pусский</a>
</div>

## Install

```shell
Expand All @@ -15,9 +20,17 @@ npm i dasha
## Quick start

```js
import fs from 'node:fs/promises';
import { parse } from 'dasha';

const url = 'https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd';
const body = await fetch(url).then((res) => res.text());
const manifest = await parse(body, url);

for (const track of manifest.tracks.all) {
for (const segment of track.segments) {
const content = await fetch(url).then((res) => res.arrayBuffer());
await fs.appendFile(`${track.id}.mp4`, content);
}
}
```
3 changes: 2 additions & 1 deletion dasha.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

const dash = require('./lib/dash');
const hls = require('./lib/hls');
const { filterByResolution, filterByQuality, filterByCodecs } = require('./lib/track');

const parse = (text, url, fallbackLanguage) => {
if (text.includes('<MPD')) return dash.parseManifest(text, url, fallbackLanguage);
else if (text.includes('#EXTM3U')) return hls.parseManifest(text, url);
else throw new Error('Invalid manifest');
};

module.exports = { parse };
module.exports = { parse, filterByResolution, filterByQuality, filterByCodecs };
36 changes: 36 additions & 0 deletions docs/README.ru.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# dasha

[![npm version](https://img.shields.io/npm/v/dasha?style=flat&color=white)](https://www.npmjs.com/package/dasha)
[![npm downloads/month](https://img.shields.io/npm/dm/dasha?style=flat&color=white)](https://www.npmjs.com/package/dasha)
[![npm downloads](https://img.shields.io/npm/dt/dasha?style=flat&color=white)](https://www.npmjs.com/package/dasha)

Библиотека для парсинга MPEG-DASH и HLS манифестов. Создана с целью получения упрощенного представления, удобного для дальнейшей загрузки сегментов.

<div align="left">
<a href="https://github.com/vitalygashkov/dasha/tree/main/README.md">English</a> •
<span>Русский</span>
</div>

## Установка

```shell
npm i dasha
```

## Быстрый старт

```js
import fs from 'node:fs/promises';
import { parse } from 'dasha';

const url = 'https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd';
const body = await fetch(url).then((res) => res.text());
const manifest = await parse(body, url);

for (const track of manifest.tracks.all) {
for (const segment of track.segments) {
const content = await fetch(url).then((res) => res.arrayBuffer());
await fs.appendFile(`${track.id}.mp4`, content);
}
}
```
30 changes: 22 additions & 8 deletions lib/dash.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const {
createVideoQualityFilter,
createAudioLanguageFilter,
createSubtitleLanguageFilter,
createVideoCodecFilter,
createAudioCodecFilter,
} = require('./track');

const appendUtils = (element) => {
Expand Down Expand Up @@ -173,10 +175,14 @@ const parseSegmentsFromTemplate = (
const startNumber = Number(segmentTemplate.get('startNumber') || 1);
const segmentTimeline = segmentTemplate.get('SegmentTimeline');
resolveSegmentTemplateUrls(segmentTemplate, baseUrl, manifestUrl);
if (!duration) throw new Error('Duration of the Period was unable to be determined.');
const segmentDuration = parseFloat(segmentTemplate.get('duration'));
const segmentTimescale = parseFloat(segmentTemplate.get('timescale') || 1);
const segmentsCount = Math.ceil(duration / (segmentDuration / segmentTimescale));
// TODO: Support live manifests with type=dynamic
const DEFAULT_SEGMENTS_COUNT = 35;
// if (!duration) throw new Error('Duration of the Period was unable to be determined.');
const segmentsCount = duration
? Math.ceil(duration / (segmentDuration / segmentTimescale))
: DEFAULT_SEGMENTS_COUNT;
const bandwidth = representation.get('bandwidth');
const id = representation.get('id');
const segments = [];
Expand Down Expand Up @@ -222,12 +228,13 @@ const parseSegmentFromBase = async (segmentBase, baseUrl) => {
const initialization = segmentBase.get('Initialization');
let mediaRange = '';
if (initialization) {
const range = initialization.get('range');
const headers = range ? { Range: `bytes=${range}` } : undefined;
const response = await fetch(baseUrl, headers);
const initData = await response.arrayBuffer();
const totalSize = response.headers.get('Content-Range').split('/')[-1];
if (totalSize) mediaRange = `${initData.byteLength}-${totalSize}`;
// const range = initialization.get('range');
// const headers = range ? { Range: `bytes=${range}` } : undefined;
// const response = await fetch(baseUrl, headers);
// const initData = await response.arrayBuffer();
// console.log(response.headers);
// const totalSize = response.headers.get('Content-Range').split('/')[-1];
// if (totalSize) mediaRange = `${initData.byteLength}-${totalSize}`;
}
return { url: baseUrl, range: mediaRange };
};
Expand Down Expand Up @@ -398,6 +405,11 @@ const parseManifest = async (text, url, fallbackLanguage) => {
}
}
}

videos.sort((a, b) => b.bitrate.bps - a.bitrate.bps);
audios.sort((a, b) => b.bitrate.bps - a.bitrate.bps);
subtitles.sort((a, b) => b.bitrate.bps - a.bitrate.bps);

return {
duration,
tracks: {
Expand All @@ -406,7 +418,9 @@ const parseManifest = async (text, url, fallbackLanguage) => {
audios,
subtitles,
withResolution: createResolutionFilter(videos),
withVideoCodecs: createVideoCodecFilter(videos),
withVideoQuality: createVideoQualityFilter(videos),
withAudioCodecs: createAudioCodecFilter(audios),
withAudioLanguages: createAudioLanguageFilter(audios),
withSubtitleLanguages: createSubtitleLanguageFilter(subtitles),
},
Expand Down
25 changes: 24 additions & 1 deletion lib/track.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

const { getWidth, getBestTrack } = require('./util');
const { getBestTrack } = require('./util');

const parseMimes = (codecs) =>
codecs
Expand All @@ -16,15 +16,29 @@ const createResolutionFilter = (videos) => {
};
};

const createCodecFilter = (tracks) => {
return (codecs) => {
if (!codecs?.length) return tracks;
const results = tracks.filter((track) => codecs.includes(track.codec));
results.sort((a, b) => b.bitrate.bps - a.bitrate.bps);
return results;
};
};

const createVideoCodecFilter = (videos) => createCodecFilter(videos);

const createVideoQualityFilter = (videos) => {
return (quality) => {
if (!quality) return [getBestTrack(videos)];
const trackQuality = String(quality).includes('p') ? quality : `${quality}p`;
const results = videos.filter((track) => track.quality === trackQuality);
results.sort((a, b) => b.bitrate.bps - a.bitrate.bps);
return results.length ? results : [getBestTrack(videos)];
};
};

const createAudioCodecFilter = (audios) => createCodecFilter(audios);

const createAudioLanguageFilter = (audios) => {
return (languages = [], maxTracksPerLanguage) => {
if (!languages.length) {
Expand Down Expand Up @@ -61,10 +75,19 @@ const createSubtitleLanguageFilter = (subtitles) => {
};
};

const filterByResolution = (tracks, resolution) => createResolutionFilter(tracks)(resolution);
const filterByQuality = (tracks, quality) => createVideoQualityFilter(tracks)(quality);
const filterByCodecs = (tracks, codecs) => createCodecFilter(tracks)(codecs);

module.exports = {
parseMimes,
createResolutionFilter,
createVideoCodecFilter,
createVideoQualityFilter,
createAudioCodecFilter,
createAudioLanguageFilter,
createSubtitleLanguageFilter,
filterByResolution,
filterByQuality,
filterByCodecs,
};
16 changes: 6 additions & 10 deletions lib/xml.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function parse(text, options = {}) {

var closeTag = text.substring(closeStart, pos);
if (closeTag.indexOf(tagName) == -1) {
var parsedText = text.substring(0, pos).split('\n');
const parsedText = text.substring(0, pos).split('\n');
throw new Error(
'Unexpected close tag\nLine: ' +
(parsedText.length - 1) +
Expand Down Expand Up @@ -103,7 +103,7 @@ function parse(text, options = {}) {
node.children = [];
}
} else {
var parsedText = parseText();
const parsedText = parseText();
if (keepWhitespace) {
if (parsedText.length > 0) {
children.push(parsedText);
Expand Down Expand Up @@ -191,12 +191,12 @@ function parse(text, options = {}) {
// optional parsing of children
if (text.charCodeAt(pos - 1) !== slashCC) {
if (tagName == 'script') {
var start = pos + 1;
const start = pos + 1;
pos = text.indexOf('</script>', pos);
children = [text.slice(start, pos)];
pos += 9;
} else if (tagName == 'style') {
var start = pos + 1;
const start = pos + 1;
pos = text.indexOf('</style>', pos);
children = [text.slice(start, pos)];
pos += 8;
Expand Down Expand Up @@ -241,10 +241,10 @@ function parse(text, options = {}) {
}
}

var out = null;
let out = null;
if (options.attrValue !== undefined) {
options.attrName = options.attrName || 'id';
var out = [];
out = [];

while ((pos = findElements()) !== -1) {
pos = text.lastIndexOf('<', pos);
Expand All @@ -264,10 +264,6 @@ function parse(text, options = {}) {
out = filter(out, options.filter);
}

if (options.simplify) {
return simplify(Array.isArray(out) ? out : [out]);
}

if (options.setPos) {
out.pos = pos;
}
Expand Down
2 changes: 1 addition & 1 deletion test/dasha.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ test('DASH: parse without URL parameter', async () => {
const manifest = await parse(text);
strictEqual(manifest.tracks.all.length, 8);
const initUrl =
'https://a-vrv.akamaized.net/evs3/8a1b3acce53d49eea0ce2104fae30046/assets/p/c46e06c5fd496e8aec0b6776b97eca3f_,3748583.mp4,3748584.mp4,3748582.mp4,3748580.mp4,3748581.mp4,.urlset/init-f1-v1-x3.mp4?t=exp=1713871228~acl=/evs3/8a1b3acce53d49eea0ce2104fae30046/assets/p/c46e06c5fd496e8aec0b6776b97eca3f_,3748583.mp4,3748584.mp4,3748582.mp4,3748580.mp4,3748581.mp4,.urlset/*~hmac=7dc7daeb338da040c65111a88cbf947505a5897f42d1c433c3858f8d890ed29c';
'https://a-vrv.akamaized.net/evs3/8a1b3acce53d49eea0ce2104fae30046/assets/p/c46e06c5fd496e8aec0b6776b97eca3f_,3748583.mp4,3748584.mp4,3748582.mp4,3748580.mp4,3748581.mp4,.urlset/init-f2-v1-x3.mp4?t=exp=1713871228~acl=/evs3/8a1b3acce53d49eea0ce2104fae30046/assets/p/c46e06c5fd496e8aec0b6776b97eca3f_,3748583.mp4,3748584.mp4,3748582.mp4,3748580.mp4,3748581.mp4,.urlset/*~hmac=7dc7daeb338da040c65111a88cbf947505a5897f42d1c433c3858f8d890ed29c';
strictEqual(manifest.tracks.videos[0].segments[0].url, initUrl);
});
7 changes: 7 additions & 0 deletions types/dasha.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,19 @@ export interface Manifest {
audios: AudioTrack[];
subtitles: SubtitleTrack[];
withResolution(resolution: { width?: string; height?: string }): VideoTrack[];
withVideoCodecs(codecs: VideoCodec[]): VideoTrack[];
withVideoQuality(quality: number | string): VideoTrack[];
withAudioCodecs(codecs: AudioCodec[]): AudioTrack[];
withAudioLanguages(languages: string[], maxTracksPerLanguage?: number): AudioTrack[];
withSubtitleLanguages(languages: string[]): SubtitleTrack[];
};
}

export function filterByResolution(resolution: { width?: string; height?: string }): VideoTrack[];
export function filterByCodecs(tracks: VideoTrack[], codecs: VideoCodec[]): VideoTrack[];
export function filterByCodecs(tracks: AudioTrack[], codecs: AudioCodec[]): AudioTrack[];
export function filterByQuality(tracks: VideoTrack[], quality: number | string): VideoTrack[];

export interface Track {
id: string;
type: 'video' | 'audio' | 'text';
Expand Down

0 comments on commit bc9218e

Please sign in to comment.