Compare commits

...

3 Commits

Author SHA1 Message Date
Kuba Orlik 51383ca07f 0.0.2 2021-10-25 21:44:34 +02:00
Kuba Orlik 7d063ad8fd add blanks 2021-10-25 21:44:27 +02:00
Kuba Orlik 84cd5014dc Entries and playlists 2021-10-25 21:26:23 +02:00
7 changed files with 147 additions and 10 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "kdenlive", "name": "kdenlive",
"version": "0.0.1", "version": "0.0.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "kdenlive", "name": "kdenlive",
"version": "0.0.1", "version": "0.0.2",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"zx": "^4.2.0" "zx": "^4.2.0"

View File

@ -1,6 +1,6 @@
{ {
"name": "kdenlive", "name": "kdenlive-ts",
"version": "0.0.1", "version": "0.0.2",
"description": "Create kdenlive projects from within JS", "description": "Create kdenlive projects from within JS",
"main": "lib/index.js", "main": "lib/index.js",
"scripts": { "scripts": {
@ -9,7 +9,8 @@
"prepare": "npm run build", "prepare": "npm run build",
"test-reports": "npm run build && rm -fr .xunit coverage && npm run test -- --cover --test-report" "test-reports": "npm run build && rm -fr .xunit coverage && npm run test -- --cover --test-report"
}, },
"author": "", "author": "Kuba Orlik",
"repository": "https://git.internet-czas-dzialac.pl/icd/kdenlive-ts",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/mocha": "^9.0.0", "@types/mocha": "^9.0.0",

33
src/entry.ts Normal file
View File

@ -0,0 +1,33 @@
import { Producer } from "./producer";
export abstract class Entry {
abstract toXML(): string;
}
export class MediaEntry extends Entry {
constructor(
public producer: Producer,
public in_point: string,
public out_point: string
) {
super();
}
toXML(): string {
return /* HTML */ `<entry
producer="${this.producer.id}"
in="${this.in_point}"
out="${this.out_point}"
></entry>`;
}
}
export class BlankEntry extends Entry {
constructor(public length: string) {
super();
}
toXML(): string {
return `<blank length="${this.length}"/>`;
}
}

View File

@ -1,4 +1,5 @@
import { $ } from "zx"; import { $ } from "zx";
import { BlankEntry, MediaEntry } from "./entry";
import Project from "./kdenlive"; import Project from "./kdenlive";
describe("Kdenlive", () => { describe("Kdenlive", () => {
@ -58,4 +59,79 @@ describe("Kdenlive", () => {
await $`echo ${await project.toXML()} > 20a20v-tracks.kdenlive`; await $`echo ${await project.toXML()} > 20a20v-tracks.kdenlive`;
}); });
}); });
describe("clips", () => {
it("should generate a 1a1v project with one clip", async () => {
const project = new Project(30);
const producer = project.addProducer("/home/kuba/Videos/5min.mp4");
const video_track = project.addVideoTractor();
const audio_track = project.addAudioTractor();
const entry = new MediaEntry(
producer,
"00:00:00.000",
"00:00:01.000"
);
video_track.addEntry(entry);
audio_track.addEntry(entry);
await $`echo ${await project.toXML()} > 1s-clip.kdenlive`;
});
it("should generate a 1a1v project with two contingent clips", async () => {
const project = new Project(30);
const producer = project.addProducer("/home/kuba/Videos/5min.mp4");
const video_track = project.addVideoTractor();
const audio_track = project.addAudioTractor();
const entry = new MediaEntry(
producer,
"00:00:00.000",
"00:00:01.000"
);
video_track.addEntry(entry);
audio_track.addEntry(entry);
const entry2 = new MediaEntry(
producer,
"00:00:01.000",
"00:00:02.000"
);
video_track.addEntry(entry2);
audio_track.addEntry(entry2);
await $`echo ${await project.toXML()} > 2x1s-clip.kdenlive`;
});
it("should generate a 1a1v project with 20 contingent clips", async () => {
const project = new Project(30);
const producer = project.addProducer("/home/kuba/Videos/5min.mp4");
const video_track = project.addVideoTractor();
const audio_track = project.addAudioTractor();
for (let i = 0; i <= 20; i++) {
let entry = new MediaEntry(
producer,
`00:00:${i.toString().padStart(2, "0")}.000`,
`00:00:${(i + 1).toString().padStart(2, "0")}.000`
);
video_track.addEntry(entry);
audio_track.addEntry(entry);
}
await $`echo ${await project.toXML()} > 20x1s-clip.kdenlive`;
});
it("should generate a 1a1v project with 10 clips with 1s pauses", async () => {
const project = new Project(30);
const producer = project.addProducer("/home/kuba/Videos/5min.mp4");
const video_track = project.addVideoTractor();
const audio_track = project.addAudioTractor();
for (let i = 0; i <= 20; i++) {
let entry = new MediaEntry(
producer,
`00:00:${i.toString().padStart(2, "0")}.000`,
`00:00:${(i + 1).toString().padStart(2, "0")}.000`
);
video_track.addEntry(entry);
audio_track.addEntry(entry);
video_track.addEntry(new BlankEntry("00:00:01.000"));
audio_track.addEntry(new BlankEntry("00:00:01.000"));
}
await $`echo ${await project.toXML()} > 10x1s-clip-with-breaks.kdenlive`;
});
});
}); });

View File

@ -1,23 +1,36 @@
import { Entry } from "./entry";
import { makeIDGen } from "./util"; import { makeIDGen } from "./util";
const playlistIndexGen = makeIDGen(0); const playlistIndexGen = makeIDGen(0);
export abstract class Playlist { export abstract class Playlist {
public entries: Entry[] = [];
constructor(public index = playlistIndexGen.next().value) {} constructor(public index = playlistIndexGen.next().value) {}
abstract toXML(): string; abstract toXML(): string;
addEntry(entry: Entry) {
this.entries.push(entry);
}
renderEntries() {
return this.entries.map((e) => e.toXML()).join("\n");
}
} }
export class AudioPlaylist extends Playlist { export class AudioPlaylist extends Playlist {
toXML() { toXML() {
return /* HTML */ ` <playlist id="playlist${this.index}"> return /* HTML */ `<playlist id="playlist${this.index}">
<property name="kdenlive:audio_track">1</property> <property name="kdenlive:audio_track">1</property>
${this.renderEntries()}
</playlist>`; </playlist>`;
} }
} }
export class VideoPlaylist extends Playlist { export class VideoPlaylist extends Playlist {
toXML() { toXML() {
return /* HTML */ ` <playlist id="playlist${this.index}"></playlist>`; return /* HTML */ ` <playlist id="playlist${this.index}">
${this.renderEntries()}
</playlist>`;
} }
} }

View File

@ -9,18 +9,22 @@ export abstract class Producer {
this.index = producerIndexGen.next().value; this.index = producerIndexGen.next().value;
} }
async getNativeMltXml(fps: number) { async getNativeMltXml(fps: number): Promise<string> {
const xml = ( const xml = (
await $`melt ${ await $`melt ${
this.path this.path
} -consumer xml ${`frame_rate_num=${fps}`} | htmlq producer` } -consumer xml ${`frame_rate_num=${fps}`} | htmlq producer`
).stdout; ).stdout;
return xml.replace("producer0", `producer${this.index}`); return xml.replace("producer0", this.id);
} }
async toXML(fps: number) { async toXML(fps: number): Promise<string> {
return await this.getNativeMltXml(fps); return await this.getNativeMltXml(fps);
} }
get id() {
return "producer" + this.index;
}
} }
export class ConcreteProducer extends Producer { export class ConcreteProducer extends Producer {
@ -87,4 +91,8 @@ export class BlackTrack extends Producer {
<property name="set.test_audio">0</property> <property name="set.test_audio">0</property>
</producer>`; </producer>`;
} }
get id() {
return "black_track";
}
} }

View File

@ -1,3 +1,4 @@
import { Entry } from "./entry";
import { AudioPlaylist, Playlist, VideoPlaylist } from "./playlist"; import { AudioPlaylist, Playlist, VideoPlaylist } from "./playlist";
import { makeIDGen } from "./util"; import { makeIDGen } from "./util";
@ -9,6 +10,11 @@ export abstract class Tractor {
public index = trackIndexGen.next().value; public index = trackIndexGen.next().value;
abstract toXML(): string; abstract toXML(): string;
addEntry(entry: Entry): this {
this.main_playlist.addEntry(entry);
return this;
}
} }
export class AudioTractor extends Tractor { export class AudioTractor extends Tractor {