Compare commits
9
Commits
c68b887b60
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63fda950e5 | ||
|
|
5b4b36c928 | ||
|
|
f3f88edc0f | ||
|
|
1eda5b676f | ||
|
|
702a92ccd5 | ||
|
|
e78b2368ae | ||
|
|
71de57af38 | ||
|
|
fe2db3e1d1 | ||
|
|
df6087d0d8 |
@@ -0,0 +1,151 @@
|
||||
# Video Encoder (videnc)
|
||||
|
||||
A Deno-based video encoding application that converts videos to the AV1 video
|
||||
codec with Opus audio in MKV containers.
|
||||
|
||||
## Features
|
||||
|
||||
- Encodes videos to AV1 (libsvtav1) for high efficiency
|
||||
- Converts audio to Opus for excellent quality at low bitrates
|
||||
- Automatically detects video properties (resolution, frame rate, interlacing,
|
||||
anamorphic content)
|
||||
- Applies appropriate filters for interlaced and anamorphic content
|
||||
- Preserves and embeds metadata
|
||||
- Integrates with OMDB API for automatic metadata fetching
|
||||
- Generates shell scripts for encoding commands
|
||||
- Supports multiple audio and subtitle streams
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Deno](https://deno.com/) v1.0 or higher
|
||||
- [FFmpeg](https://ffmpeg.org/) with libsvtav1 support
|
||||
- [FFprobe](https://ffmpeg.org/ffprobe.html) (usually bundled with FFmpeg)
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd videenc_ts
|
||||
```
|
||||
|
||||
2. Ensure you have Deno installed:
|
||||
```bash
|
||||
deno --version
|
||||
```
|
||||
|
||||
3. Ensure you have FFmpeg with libsvtav1 installed:
|
||||
```bash
|
||||
ffmpeg -version
|
||||
ffmpeg -h encoder=libsvtav1
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
./videnc -i <input_video_file>
|
||||
```
|
||||
|
||||
This will analyze the video and generate a shell script (`encode.sh` or
|
||||
`encode_N.sh`) containing the FFmpeg command for encoding.
|
||||
|
||||
### With OMDB Metadata
|
||||
|
||||
To use OMDB metadata:
|
||||
|
||||
1. Get an API key from [OMDB API](http://www.omdbapi.com/apikey.aspx)
|
||||
2. Add it to your settings file at `~/.config/videnc/settings.json`:
|
||||
```json
|
||||
{
|
||||
"omdb_api_key": "your_api_key_here"
|
||||
}
|
||||
```
|
||||
3. Run the encoder with a video file:
|
||||
```bash
|
||||
./videnc -i <input_video_file>
|
||||
```
|
||||
4. When prompted, enter the IMDb ID for the video (e.g., tt0111161 for The
|
||||
Shawshank Redemption)
|
||||
|
||||
## Building
|
||||
|
||||
You can build the application in two ways:
|
||||
|
||||
### Using Make
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
### Using Deno Compile
|
||||
|
||||
```bash
|
||||
deno compile --allow-all --output ./videnc ./src/main.ts
|
||||
```
|
||||
|
||||
Both methods will create a standalone executable named `videnc`.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Video Analysis**: Uses FFprobe to analyze the input video file and extract
|
||||
technical information
|
||||
2. **Metadata Fetching**: Optionally fetches metadata from OMDB API using the
|
||||
provided IMDb ID
|
||||
3. **Profile Selection**: Automatically selects encoding profiles based on video
|
||||
resolution:
|
||||
- DVD profile for videos with less than 1 megapixel
|
||||
- BluRay profile for higher resolution videos
|
||||
4. **Command Generation**: Creates an optimized FFmpeg command with appropriate
|
||||
settings for:
|
||||
- Video encoding (AV1 with libsvtav1)
|
||||
- Audio encoding (Opus)
|
||||
- Subtitle handling (copy)
|
||||
- Metadata embedding
|
||||
- Filters for interlaced and anamorphic content
|
||||
5. **Script Creation**: Generates a shell script with the encoding command
|
||||
|
||||
## Encoding Profiles
|
||||
|
||||
### DVD Profile
|
||||
|
||||
- CRF: 29
|
||||
- Preset: 2
|
||||
- Film Grain: 5
|
||||
|
||||
### BluRay Profile
|
||||
|
||||
- CRF: 27
|
||||
- Preset: 2
|
||||
- Film Grain: 5
|
||||
|
||||
## Example Generated Command
|
||||
|
||||
```bash
|
||||
ffmpeg \
|
||||
-hide_banner \
|
||||
-i "input_video.mp4" \
|
||||
-map_metadata -1 \
|
||||
-map 0:v \
|
||||
-c:v libsvtav1 \
|
||||
-pix_fmt yuv420p10le \
|
||||
-crf 27 \
|
||||
-svtav1-params keyint=120:tune=0:film-grain=5:scd=1:film-grain-denoise=1:enable-overlays=1:enable-qm=1:qm-min=0:qm-max=15 \
|
||||
-preset 2 \
|
||||
-vf "bwdif=mode=0,scale=iw*sar:ih:lanczos,setsar=1,scale=-2:ih:lanczos" \
|
||||
-map 0:a:0 \
|
||||
-c:a:0 libopus \
|
||||
-metadata:s:a:0 language=eng \
|
||||
-disposition:a:0 default \
|
||||
-map 0:s:0 \
|
||||
-c:s:0 copy \
|
||||
-metadata:s:s:0 language=eng \
|
||||
-disposition:s:0 -1 \
|
||||
"output_video.av1.mkv"
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
||||
for details.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Encoding Settings Changelog
|
||||
|
||||
This document tracks changes to the encoding settings used by videnc over time.
|
||||
|
||||
## Version 1.0 - Initial Release (2025-10-04)
|
||||
|
||||
### Universal Settings
|
||||
|
||||
These settings are applied to all videos regardless of source material:
|
||||
|
||||
- **Video Codec**: libsvtav1 (AV1)
|
||||
- **Pixel Format**: yuv420p10le
|
||||
- **Preset**: 4 (universal setting)
|
||||
- **enable-overlays**: 1 (enabled for all encodings)
|
||||
- **enable-qm**: 1 (enabled for all encodings)
|
||||
- **qm-min**: 0
|
||||
- **qm-max**: 15
|
||||
- **scd**: 1 (scene change detection enabled)
|
||||
- **film-grain-denoise**: 1 (enabled for all encodings)
|
||||
- **tune**: 0 (default tuning)
|
||||
|
||||
### Variable Settings
|
||||
|
||||
These settings vary based on video characteristics:
|
||||
|
||||
#### Resolution-Based Settings
|
||||
- **CRF**: 30 for videos < 1 MPixel, 28 for videos >= 1 MPixel
|
||||
- **film-grain**: 10 for videos < 1 MPixel, 5 for videos >= 1 MPixel
|
||||
|
||||
### Source Material Dependent Settings
|
||||
|
||||
These settings are conditionally applied based on the characteristics of the source video:
|
||||
|
||||
- **Deinterlacing**: Applied when `isInterlaced` is detected
|
||||
- Filter: `bwdif=mode=0`
|
||||
|
||||
- **Anamorphic Correction**: Applied when `isAnamorphic` is detected
|
||||
- Filter chain: `scale=iw*sar:ih:lanczos,setsar=1,scale=-2:ih:lanczos`
|
||||
|
||||
- **Keyframe Interval**: Dynamically calculated based on video FPS
|
||||
- Value: `Math.round(videoInfo.fps * 5)`
|
||||
|
||||
### Audio Settings
|
||||
|
||||
- **Audio Codec**: libopus for all audio streams
|
||||
- **Channel Layout**: Preserved from source (with special handling for sideloaded audio)
|
||||
- **Language Metadata**: Preserved for each stream
|
||||
- **Disposition**: English audio tracks set as default
|
||||
|
||||
### Subtitle Settings
|
||||
|
||||
- **Subtitle Handling**: Copy (no re-encoding)
|
||||
- **Language Metadata**: Preserved for each stream
|
||||
- **Disposition**: Finnish subtitle tracks set as default
|
||||
|
||||
### Metadata Handling
|
||||
|
||||
- **Metadata Stripping**: Original metadata is stripped (`-map_metadata -1`)
|
||||
- **Custom Metadata**: Added from OMDB API or user input
|
||||
- **Encoding Version**: Added as `ENCODING_VERSION=1.0`
|
||||
@@ -0,0 +1,10 @@
|
||||
.PHONY: build clean run
|
||||
|
||||
build:
|
||||
@echo "Building..."
|
||||
deno compile --allow-all --output ./videnc ./src/main.ts
|
||||
|
||||
clean:
|
||||
@echo "Cleaning..."
|
||||
rm -f ./videnc
|
||||
|
||||
+13
-9
@@ -8,14 +8,14 @@ function escapeFilename(filename: string): string {
|
||||
|
||||
const profiles: Record<string, profile> = {
|
||||
"DVD": {
|
||||
"crf": 29,
|
||||
"preset": 2,
|
||||
"crf": 30,
|
||||
"preset": 4,
|
||||
"filmGrain": 5,
|
||||
},
|
||||
"BluRay": {
|
||||
"crf": 27,
|
||||
"preset": 2,
|
||||
"filmGrain": 5,
|
||||
"crf": 28,
|
||||
"preset": 4,
|
||||
"filmGrain": 10,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -28,8 +28,8 @@ export function encodingCommandBuilder(videoInfo: parsedVideoInfo): string {
|
||||
const profile = videoInfo.MPix < 1 ? profiles.DVD : profiles.BluRay;
|
||||
const args = [
|
||||
"ffmpeg \\\n",
|
||||
"-v",
|
||||
"quiet \\\n",
|
||||
// "-v",
|
||||
// "quiet \\\n",
|
||||
"-hide_banner \\\n",
|
||||
"-i",
|
||||
`"${videoInfo.filename}" \\\n`,
|
||||
@@ -45,7 +45,9 @@ export function encodingCommandBuilder(videoInfo: parsedVideoInfo): string {
|
||||
args.push("-crf", profile.crf + " \\\n");
|
||||
args.push(
|
||||
"-svtav1-params",
|
||||
`tune=0:film-grain=${profile.filmGrain}:scd=1:film-grain-denoise=1 \\\n`,
|
||||
`keyint=${
|
||||
Math.round(videoInfo.fps * 5)
|
||||
}:tune=0:film-grain=${profile.filmGrain}:scd=1:film-grain-denoise=1:enable-overlays=1:enable-qm=1:qm-min=0:qm-max=15 \\\n`,
|
||||
);
|
||||
args.push("-preset", profile.preset + " \\\n");
|
||||
|
||||
@@ -65,7 +67,7 @@ export function encodingCommandBuilder(videoInfo: parsedVideoInfo): string {
|
||||
);
|
||||
}
|
||||
|
||||
args.push("-g", `${Math.round(videoInfo.fps * 5)} \\\n`);
|
||||
// args.push("-g", `${Math.round(videoInfo.fps * 5)} \\\n`);
|
||||
|
||||
if (videoInfo.metadata) {
|
||||
args.push("-metadata", `title="${videoInfo.metadata.title}" \\\n`);
|
||||
@@ -80,6 +82,8 @@ export function encodingCommandBuilder(videoInfo: parsedVideoInfo): string {
|
||||
);
|
||||
}
|
||||
|
||||
args.push("-metadata", "ENCODING_VERSION=1.0 \\\n");
|
||||
|
||||
for (const [index, audioStream] of videoInfo.audioStreams?.entries() ?? []) {
|
||||
args.push(
|
||||
"-map",
|
||||
|
||||
+26
-5
@@ -5,11 +5,29 @@ import {
|
||||
OMDBMovieInfo,
|
||||
OMDBSeriesInfo,
|
||||
parsedVideoInfo,
|
||||
settings,
|
||||
VideoInfo,
|
||||
} from "./types.ts";
|
||||
import { VideoParser } from "./videoParser.ts";
|
||||
import { encodingCommandBuilder } from "./commandBuilder.ts";
|
||||
|
||||
const settings_file = Deno.env.get("HOME") + "/.config/videnc/settings.json";
|
||||
|
||||
function loadSettings(): settings {
|
||||
if (!exists(settings_file)) {
|
||||
const settings: settings = { omdb_api_key: "" };
|
||||
Deno.writeFileSync(
|
||||
settings_file,
|
||||
new TextEncoder().encode(JSON.stringify(settings)),
|
||||
);
|
||||
return settings;
|
||||
}
|
||||
const settings: settings = JSON.parse(
|
||||
new TextDecoder().decode(Deno.readFileSync(settings_file)),
|
||||
);
|
||||
return settings;
|
||||
}
|
||||
|
||||
function analyzeVideo(videoFile: string): VideoInfo {
|
||||
const cmd = "ffprobe";
|
||||
const args = [
|
||||
@@ -70,23 +88,26 @@ async function writeShellScript(command: string) {
|
||||
}
|
||||
}
|
||||
Deno.writeTextFileSync(`./encode_${counter}.sh`, command);
|
||||
Deno.chmod(`./encode_${counter}.sh`, 0o755);
|
||||
console.log(`Created ./encode_${counter}.sh`);
|
||||
return;
|
||||
}
|
||||
const script = `#!/bin/bash\n${command}`;
|
||||
Deno.writeTextFileSync("./encode.sh", script);
|
||||
Deno.chmod("./encode.sh", 0o755);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Parse arguments
|
||||
const args = parseArgs(Deno.args);
|
||||
if (!args.i) {
|
||||
console.log("Usage: videnc -i <video_file> -k <omdb_api_key>");
|
||||
console.log("Usage: videnc -i <video_file>");
|
||||
Deno.exit(1);
|
||||
}
|
||||
if (!args.k) {
|
||||
const app_settings = loadSettings();
|
||||
if (!app_settings.omdb_api_key) {
|
||||
const confirm = prompt(
|
||||
"No key entered. Are you sure you want to continue? (y/N)",
|
||||
"No key found. Are you sure you want to continue? (y/N)",
|
||||
);
|
||||
if (confirm !== "y") {
|
||||
Deno.exit(1);
|
||||
@@ -101,11 +122,11 @@ async function main() {
|
||||
// Analyze video
|
||||
const videoInfo = analyzeVideo(videoFile);
|
||||
videoInfo.format.filename = resolve(videoFile);
|
||||
if (args.k) {
|
||||
if (app_settings.omdb_api_key) {
|
||||
const imdbID = prompt("Enter IMDb ID. Leave empty to skip");
|
||||
let jsonVideoInfo;
|
||||
if (imdbID) {
|
||||
const movieInfo = await fetchMovieInfo(imdbID, args.k);
|
||||
const movieInfo = await fetchMovieInfo(imdbID, app_settings.omdb_api_key);
|
||||
jsonVideoInfo = buildVideoInfo(videoInfo, movieInfo);
|
||||
} else {
|
||||
jsonVideoInfo = buildVideoInfo(videoInfo);
|
||||
|
||||
@@ -230,3 +230,7 @@ export interface profile {
|
||||
preset: number;
|
||||
filmGrain: number;
|
||||
}
|
||||
|
||||
export interface settings {
|
||||
omdb_api_key: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user