6 Commits

9 changed files with 219 additions and 212 deletions
+5 -2
View File
@@ -3,10 +3,13 @@
"build": {
"dockerfile": "Dockerfile"
},
"settings": {},
"customizations": {
"vscode": {
"extensions": ["ms-python.python"]
"extensions": [
"ms-python.python",
"vivaxy.vscode-conventional-commits",
"esbenp.prettier-vscode"
]
}
},
"postCreateCommand": "sudo apt update; sudo apt install ffmpeg -y; pip install -r requirements.txt; curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64; chmod +x tailwindcss-linux-x64; mv tailwindcss-linux-x64 tailwindcss",
+46
View File
@@ -0,0 +1,46 @@
name: Release
on:
pull_request:
branches:
- master
types: [closed]
jobs:
release:
if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'v')
runs-on: build-htz-01
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Extract Version
id: get_version
run: echo "VERSION=${{ github.head_ref }}" >> $GITHUB_OUTPUT
- name: Generate Changelog
id: changelog
uses: https://github.com/metcalfc/changelog-generator@v4.6.2
with:
myToken: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
run: |
VERSION=${{ steps.get_version.outputs.VERSION }}
docker build -t cr.jdbnet.co.uk/public/encoder:$VERSION \
-t cr.jdbnet.co.uk/public/encoder:latest \
--build-arg VERSION=$VERSION \
.
docker push cr.jdbnet.co.uk/public/encoder:$VERSION
docker push cr.jdbnet.co.uk/public/encoder:latest
- name: Create Gitea Release
uses: https://gitea.com/actions/gitea-release-action@v1
with:
tag_name: ${{ steps.get_version.outputs.VERSION }}
name: ${{ steps.get_version.outputs.VERSION }}
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false
-47
View File
@@ -1,47 +0,0 @@
name: CI
on:
push:
branches: [ master ]
jobs:
build-and-push:
name: Build and Push
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/jdb-net/encoder:${{ github.sha }}
ghcr.io/jdb-net/encoder:latest
deploy:
name: Deploy
needs: build-and-push
runs-on: [ k3s-media-lan-01 ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Apply manifests
run: |
sudo kubectl replace -f deployment.yml --grace-period=60 --force
+3
View File
@@ -1,6 +1,9 @@
FROM python:3.13-slim
LABEL org.opencontainers.image.vendor="JDB-NET"
WORKDIR /app
COPY . /app
ARG VERSION=dev
ENV APP_VERSION=${VERSION}
RUN pip install -r requirements.txt
RUN apt-get update && apt-get install -y curl ffmpeg procps
RUN rm -rf /var/lib/apt/lists/*
+118 -23
View File
@@ -18,6 +18,11 @@ load_dotenv()
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'your-secret-key-here')
ENCODING_ENABLED = os.environ.get('ENABLE_ENCODING', 'true').lower() in ('true', '1', 'yes')
@app.context_processor
def inject_version():
return {'version': os.environ.get('VERSION', 'dev')}
DB_CONFIG = {
'host': os.environ.get('DB_HOST', 'localhost'),
@@ -77,12 +82,19 @@ class DatabaseManager:
watch_folder VARCHAR(500) NOT NULL,
ffmpeg_flags TEXT DEFAULT NULL,
temp_dir VARCHAR(500) DEFAULT '/temp',
target_resolution VARCHAR(20),
enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
""")
# Add target_resolution column if it doesn't exist (for existing users)
try:
cursor.execute("ALTER TABLE encoder_config ADD COLUMN target_resolution VARCHAR(20)")
except:
pass # Column already exists
cursor.execute("""
CREATE TABLE IF NOT EXISTS job_reports (
id INT AUTO_INCREMENT PRIMARY KEY,
@@ -96,10 +108,17 @@ class DatabaseManager:
status ENUM('success', 'failed', 'processing', 'skipped') NOT NULL,
error_message TEXT,
processing_time DECIMAL(10,2),
target_resolution VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# Add target_resolution column if it doesn't exist (for existing users)
try:
cursor.execute("ALTER TABLE job_reports ADD COLUMN target_resolution VARCHAR(20)")
except:
pass # Column already exists
# New persistent job queue table
cursor.execute("""
CREATE TABLE IF NOT EXISTS job_queue (
@@ -176,20 +195,20 @@ class DatabaseManager:
conn.close()
return result
def add_config(self, encoder_type, watch_folder, ffmpeg_flags=None, temp_dir='/temp'):
def add_config(self, encoder_type, watch_folder, ffmpeg_flags=None, temp_dir='/temp', target_resolution=None):
"""Add a new watch folder configuration"""
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute(
"INSERT INTO encoder_config (encoder_type, watch_folder, ffmpeg_flags, temp_dir) VALUES (%s, %s, %s, %s)",
(encoder_type, watch_folder, ffmpeg_flags, temp_dir)
"INSERT INTO encoder_config (encoder_type, watch_folder, ffmpeg_flags, temp_dir, target_resolution) VALUES (%s, %s, %s, %s, %s)",
(encoder_type, watch_folder, ffmpeg_flags, temp_dir, target_resolution)
)
conn.commit()
cursor.close()
conn.close()
def add_job_report(self, encoder_type, file_path, original_size, encoded_size,
original_format, encoded_format, status, error_message=None, processing_time=None):
original_format, encoded_format, status, error_message=None, processing_time=None, target_resolution=None):
"""Add a job report to the database"""
if status == 'skipped':
size_saved = 0
@@ -200,10 +219,10 @@ class DatabaseManager:
cursor.execute("""
INSERT INTO job_reports
(encoder_type, file_path, original_size, encoded_size, size_saved,
original_format, encoded_format, status, error_message, processing_time)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
original_format, encoded_format, status, error_message, processing_time, target_resolution)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (encoder_type, file_path, original_size, encoded_size, size_saved,
original_format, encoded_format, status, error_message, processing_time))
original_format, encoded_format, status, error_message, processing_time, target_resolution))
conn.commit()
cursor.close()
conn.close()
@@ -324,12 +343,38 @@ class VideoEncoder:
logger.error(f"Error checking codec for {file_path}: {e}")
return False
def encode_to_h265(self, input_path, output_path, ffmpeg_flags=None):
"""Encode video to H265 MKV format (only 720p and higher), using user ffmpeg_flags if provided"""
def is_h265(self, file_path):
"""Check if video file is H265 encoded"""
try:
result = subprocess.run([
'ffprobe', '-v', 'quiet', '-select_streams', 'v:0',
'-show_entries', 'stream=codec_name', '-of', 'csv=p=0',
file_path
], capture_output=True, text=True)
return result.stdout.strip() == 'hevc'
except Exception as e:
logger.error(f"Error checking codec for {file_path}: {e}")
return False
def get_resolution(self, file_path):
"""Get video resolution (height)"""
try:
result = subprocess.run([
'ffprobe', '-v', 'quiet', '-select_streams', 'v:0',
'-show_entries', 'stream=height', '-of', 'csv=p=0',
file_path
], capture_output=True, text=True)
return int(result.stdout.strip())
except Exception as e:
logger.error(f"Error getting resolution for {file_path}: {e}")
return None
def encode_to_h265(self, input_path, output_path, ffmpeg_flags=None, target_resolution=None):
"""Encode video to H265 MKV format (only 720p and higher), with optional target resolution scaling"""
try:
resolution_check = subprocess.run([
'ffprobe', '-v', 'quiet', '-select_streams', 'v:0',
'-show_entries', 'stream=height', '-of', 'csv=p=0',
'-show_entries', 'stream=height,width', '-of', 'csv=p=0',
input_path
], capture_output=True, text=True)
@@ -337,17 +382,31 @@ class VideoEncoder:
return False, "Could not determine video resolution"
try:
height = int(resolution_check.stdout.strip())
width, height = map(int, resolution_check.stdout.strip().split(','))
if height < 720:
return False, f"Video resolution {height}p is below 720p minimum, skipping encoding"
except ValueError:
return False, "Invalid video height information"
return False, "Invalid video resolution information"
cmd = [
'ffmpeg', '-i', input_path, '-c:v', 'libx265', '-c:a', 'copy',
'-preset', 'medium', '-crf', '28', '-f', 'matroska'
]
# Add scaling filter if target resolution is specified
if target_resolution:
# target_resolution should be in format like "720p", "1080p", "480p"
target_height = int(target_resolution.replace('p', '').lower())
# Only scale down, never scale up
if target_height > 0 and height > target_height:
# Calculate scaled width to maintain aspect ratio
scale_width = int((width / height) * target_height)
# Ensure width is divisible by 2 (required by video codecs)
if scale_width % 2 != 0:
scale_width -= 1
cmd.insert(2, f'scale={scale_width}:{target_height}')
cmd.insert(2, '-vf')
# Insert user flags if provided
if ffmpeg_flags:
import shlex
@@ -493,6 +552,12 @@ class AudioEncoder:
def process_file(encoder_type, file_path, encoder):
"""Process a single file"""
global current_jobs
if not ENCODING_ENABLED:
logger.info(f"Encoding is disabled. Skipping {file_path}")
db_manager.remove_from_queue(encoder_type, file_path)
with job_lock:
current_jobs[encoder_type] = None
return
try:
if not os.path.exists(file_path):
logger.warning(f"File {file_path} no longer exists, skipping")
@@ -504,14 +569,16 @@ def process_file(encoder_type, file_path, encoder):
original_size = os.path.getsize(file_path)
file_name = os.path.basename(file_path)
file_dir = os.path.dirname(file_path)
# Get ffmpeg_flags and temp_dir for this file's folder
# Get ffmpeg_flags, temp_dir, and target_resolution for this file's folder
configs = db_manager.get_config(encoder_type)
ffmpeg_flags = None
temp_dir = DEFAULT_TEMP_DIR
target_resolution = None
for config in configs:
if file_path.startswith(config['watch_folder']):
ffmpeg_flags = config.get('ffmpeg_flags')
temp_dir = config.get('temp_dir') or DEFAULT_TEMP_DIR
target_resolution = config.get('target_resolution')
break
if not os.path.exists(temp_dir):
os.makedirs(temp_dir, exist_ok=True)
@@ -519,12 +586,22 @@ def process_file(encoder_type, file_path, encoder):
temp_input = os.path.join(temp_dir, file_name)
shutil.copy2(file_path, temp_input)
if encoder_type == 'video':
if not encoder.is_h264(temp_input):
is_h264 = encoder.is_h264(temp_input)
is_h265 = encoder.is_h265(temp_input)
# Skip if neither H.264 nor H.265
if not is_h264 and not is_h265:
os.remove(temp_input)
return
# Skip H.265 files if no target resolution is set
if is_h265 and not target_resolution:
os.remove(temp_input)
return
temp_output = os.path.join(temp_dir, f"temp_{file_name}")
temp_output = os.path.splitext(temp_output)[0] + '.mkv'
success, error = encoder.encode_to_h265(temp_input, temp_output, ffmpeg_flags=ffmpeg_flags)
success, error = encoder.encode_to_h265(temp_input, temp_output, ffmpeg_flags=ffmpeg_flags, target_resolution=target_resolution)
if success:
encoded_size = os.path.getsize(temp_output)
valid, validation_error = encoder.validate_encoded_file(temp_input, temp_output)
@@ -541,7 +618,8 @@ def process_file(encoder_type, file_path, encoder):
db_manager.add_job_report(
encoder_type, file_path, original_size, 0,
os.path.splitext(file_path)[1], '.mkv', 'failed',
error_message=f'Failed to copy encoded file: {copy_exc}'
error_message=f'Failed to copy encoded file: {copy_exc}',
target_resolution=target_resolution
)
logger.error(f"Failed to copy encoded file for {file_path}: {copy_exc}")
return
@@ -549,14 +627,16 @@ def process_file(encoder_type, file_path, encoder):
db_manager.add_job_report(
encoder_type, file_path, original_size, encoded_size,
os.path.splitext(file_path)[1], '.mkv', 'success',
processing_time=processing_time
processing_time=processing_time,
target_resolution=target_resolution
)
logger.info(f"Successfully encoded {file_path} to H265")
else:
db_manager.add_job_report(
encoder_type, file_path, original_size, 0,
os.path.splitext(file_path)[1], '.mkv', 'failed',
error_message=validation_error
error_message=validation_error,
target_resolution=target_resolution
)
logger.error(f"Validation failed for {file_path}: {validation_error}")
# Clean up temp files
@@ -571,7 +651,8 @@ def process_file(encoder_type, file_path, encoder):
db_manager.add_job_report(
encoder_type, file_path, original_size, 0,
os.path.splitext(file_path)[1], os.path.splitext(file_path)[1], 'skipped',
error_message=error
error_message=error,
target_resolution=target_resolution
)
logger.info(f"Skipped {file_path}: {error}")
else:
@@ -580,7 +661,8 @@ def process_file(encoder_type, file_path, encoder):
db_manager.add_job_report(
encoder_type, file_path, original_size, 0,
os.path.splitext(file_path)[1], '.mkv', 'failed',
error_message=error
error_message=error,
target_resolution=target_resolution
)
logger.error(f"Failed to encode {file_path}: {error}")
# Always remove temp input if it still exists
@@ -647,7 +729,8 @@ def process_file(encoder_type, file_path, encoder):
db_manager.add_job_report(
encoder_type, file_path, os.path.getsize(file_path), 0,
os.path.splitext(file_path)[1], '', 'failed',
error_message=str(e)
error_message=str(e),
target_resolution=target_resolution
)
except:
pass
@@ -686,10 +769,14 @@ def scan_folders():
audio_encoder = AudioEncoder()
while True:
if not ENCODING_ENABLED:
time.sleep(60)
continue
try:
video_configs = db_manager.get_config('video')
for config in video_configs:
folder = config['watch_folder']
target_resolution = config.get('target_resolution')
if os.path.exists(folder):
for root, dirs, files in os.walk(folder):
for file in files:
@@ -699,6 +786,14 @@ def scan_folders():
if (not db_manager.is_in_queue('video', file_path)
and not db_manager.file_already_processed(file_path)):
db_manager.add_to_queue('video', file_path)
elif target_resolution and video_encoder.is_h265(file_path):
# Also encode H.265 files if current resolution is higher than target
current_height = video_encoder.get_resolution(file_path)
target_height = int(target_resolution.replace('p', '').lower())
if current_height and current_height > target_height:
if (not db_manager.is_in_queue('video', file_path)
and not db_manager.file_already_processed(file_path)):
db_manager.add_to_queue('video', file_path)
audio_configs = db_manager.get_config('audio')
for config in audio_configs:
@@ -822,8 +917,8 @@ def edit_config(config_id):
conn = db_manager.get_connection()
cursor = conn.cursor()
cursor.execute(
"UPDATE encoder_config SET watch_folder=%s, temp_dir=%s, ffmpeg_flags=%s WHERE id=%s",
(data.get('watch_folder'), data.get('temp_dir'), data.get('ffmpeg_flags'), config_id)
"UPDATE encoder_config SET watch_folder=%s, temp_dir=%s, ffmpeg_flags=%s, target_resolution=%s WHERE id=%s",
(data.get('watch_folder'), data.get('temp_dir'), data.get('ffmpeg_flags'), data.get('target_resolution'), config_id)
)
conn.commit()
cursor.close()
-98
View File
@@ -1,98 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: encoder
namespace: encoder
spec:
replicas: 1
selector:
matchLabels:
app: encoder
template:
metadata:
labels:
app: encoder
spec:
containers:
- name: encoder
image: ghcr.io/jdb-net/encoder:latest
imagePullPolicy: Always
ports:
- containerPort: 5000
name: "encoder"
env:
- name: DB_HOST
value: "10.10.25.4"
- name: DB_USER
value: "encoder"
- name: DB_PASSWORD
value: "GUQsa9ClbW8uzP"
- name: DB_NAME
value: "encoder"
- name: SECRET_KEY
value: "IHMu3Fasz5MS3oOkcrPPlXIGyzk6qa"
ports:
- containerPort: 80
name: http
volumeMounts:
- name: temp
mountPath: /temp
- name: media
mountPath: /media
resources:
requests:
cpu: "2"
limits:
cpu: "2"
volumes:
- name: temp
persistentVolumeClaim:
claimName: encoder-temp-pvc
- name: media
nfs:
server: 10.10.2.5
path: /srv/Media
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: encoder-temp-pvc
namespace: encoder
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: encoder-ingress-service
namespace: encoder
spec:
selector:
app: encoder
ports:
- protocol: TCP
port: 80
targetPort: 5000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: encoder-ingress
namespace: encoder
spec:
rules:
- host: encoder.jdb143.uk
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: encoder-ingress-service
port:
number: 80
-35
View File
@@ -1,35 +0,0 @@
services:
app:
image: docker.jdbnet.co.uk/public/encoder:latest
restart: always
depends_on:
- db
environment:
DB_HOST: db
DB_USER: encoder_user
DB_PASSWORD: encoder_pass
DB_NAME: encoder
SECRET_KEY: your-secret-key-here
ports:
- "80:5000"
volumes:
- ./temp:/temp
# More volumes here required for media files
networks:
- encoder_net
db:
image: mariadb:11
restart: always
environment:
MYSQL_ROOT_PASSWORD: example_root_password
MYSQL_DATABASE: encoder
MYSQL_USER: encoder_user
MYSQL_PASSWORD: encoder_pass
volumes:
- ./db_data:/var/lib/mysql
networks:
- encoder_net
networks:
encoder_net:
driver: bridge
+3 -6
View File
@@ -18,7 +18,7 @@
</head>
<body class="bg-gray-950 text-white min-h-screen">
<nav class="bg-gray-950 border-b border-gray-800 modern-shadow" x-data="{ mobileMenuOpen: false }">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="max-w-full mx-auto px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<a href="{{ url_for('index') }}" class="flex items-center space-x-2">
@@ -81,11 +81,8 @@
{% block scripts %}{% endblock %}
<footer class="w-full mt-12 py-6 bg-gray-950 border-t border-gray-800 text-center text-gray-400 text-sm">
<div class="max-w-7xl mx-auto flex flex-col md:flex-row items-center justify-between gap-2 px-4">
<span>&copy; <script>document.write(new Date().getFullYear());</script> JDB-NET</span>
<div class="flex space-x-4">
<a href="https://projects.jdbnet.co.uk/encoder" target="_blank" class="hover:text-sky-400 transition-all">Docs</a>
<a href="mailto:jamie@jdbnet.co.uk?subject=Encoder" class="hover:text-fuchsia-400 transition-all">Support</a>
</div>
<span>&copy; <script>document.write(new Date().getFullYear());</script> <a href="https://www.jdbnet.co.uk" target="_blank" class="hover:text-sky-400 transition-all">JDB-NET</a></span>
<a href="https://git.jdbnet.co.uk/jamie/encoder" target="_blank" class="hover:text-sky-400 transition-all">{{ version }}</a>
</div>
</footer>
+44 -1
View File
@@ -47,6 +47,19 @@
<input type="hidden" name="temp_dir" id="video-temp-input" value="/temp">
<div id="video-temp-list" class="bg-gray-800 border border-gray-700 rounded-lg mt-1 p-2 text-white text-sm hidden"></div>
</div>
<div class="flex flex-col">
<label for="video-target-resolution" class="text-xs text-gray-400 mb-1">Target Resolution
<span class="text-gray-500 text-xs">(Optional)</span>
</label>
<select name="target_resolution" id="video-target-resolution" class="bg-gray-700 border border-gray-600 rounded-lg px-2 py-1 text-white">
<option value="">Default (Keep Original)</option>
<option value="480p">480p</option>
<option value="720p">720p</option>
<option value="1080p">1080p</option>
<option value="1440p">1440p</option>
<option value="2160p">4K (2160p)</option>
</select>
</div>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-medium">
Add Folder
</button>
@@ -99,6 +112,9 @@
{% if config.temp_dir %}
<div class="text-blue-200 text-xs mt-1">Temp Dir: <span class="bg-blue-900 px-1 rounded">{{ config.temp_dir }}</span></div>
{% endif %}
{% if config.target_resolution %}
<div class="text-green-200 text-xs mt-1">Resolution: <span class="bg-green-900 px-1 rounded">{{ config.target_resolution }}</span></div>
{% endif %}
{% if config.ffmpeg_flags %}
{% set flags = config.ffmpeg_flags.split() %}
<div class="text-blue-300 text-xs mt-1">
@@ -207,6 +223,9 @@
{% if config.temp_dir %}
<div class="text-purple-200 text-xs mt-1">Temp Dir: <span class="bg-purple-900 px-1 rounded">{{ config.temp_dir }}</span></div>
{% endif %}
{% if config.target_resolution %}
<div class="text-green-200 text-xs mt-1">Resolution: <span class="bg-green-900 px-1 rounded">{{ config.target_resolution }}</span></div>
{% endif %}
{% if config.ffmpeg_flags %}
{% set flags = config.ffmpeg_flags.split() %}
<div class="text-purple-300 text-xs mt-1">
@@ -252,6 +271,9 @@
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -279,6 +301,19 @@
<input type="hidden" name="temp_dir" id="edit-temp-input" value="/temp">
<div id="edit-temp-list" class="bg-gray-800 border border-gray-700 rounded-lg mt-1 p-2 text-white text-sm hidden z-50" style="position:absolute;"></div>
</div>
<div class="mb-4" id="edit-target-resolution-field" style="display:none;">
<label for="edit-target-resolution" class="text-xs text-gray-400 mb-1">Target Resolution
<span class="text-gray-500 text-xs">(Optional)</span>
</label>
<select name="target_resolution" id="edit-target-resolution" class="bg-gray-700 border border-gray-600 rounded-lg px-2 py-1 text-white w-full">
<option value="">Default (Keep Original)</option>
<option value="480p">480p</option>
<option value="720p">720p</option>
<option value="1080p">1080p</option>
<option value="1440p">1440p</option>
<option value="2160p">4K (2160p)</option>
</select>
</div>
<div class="mb-4" id="edit-ffmpeg-flags-video" style="display:none;">
<label class="block text-xs text-gray-400 mb-1">FFmpeg Flags (Video)</label>
<div class="flex space-x-2">
@@ -416,8 +451,11 @@ function openEditModal(config) {
document.getElementById('edit-temp-input').value = config.temp_dir || '/temp';
// Show/hide flag dropdowns
if (config.encoder_type === 'video') {
document.getElementById('edit-target-resolution-field').style.display = '';
document.getElementById('edit-ffmpeg-flags-video').style.display = '';
document.getElementById('edit-ffmpeg-flags-audio').style.display = 'none';
// Set target resolution value
document.getElementById('edit-target-resolution').value = config.target_resolution || '';
// Parse ffmpeg_flags for crf and preset
let crf = '24', preset = 'medium';
if (config.ffmpeg_flags) {
@@ -429,6 +467,7 @@ function openEditModal(config) {
document.getElementById('edit-crf').value = crf;
document.getElementById('edit-preset').value = preset;
} else {
document.getElementById('edit-target-resolution-field').style.display = 'none';
document.getElementById('edit-ffmpeg-flags-video').style.display = 'none';
document.getElementById('edit-ffmpeg-flags-audio').style.display = '';
// Parse ffmpeg_flags for audio bitrate
@@ -459,15 +498,18 @@ if (editForm) {
const id = document.getElementById('edit-config-id').value;
const encoderType = document.getElementById('edit-encoder-type').value;
let ffmpeg_flags = '';
let target_resolution = '';
if (encoderType === 'video') {
ffmpeg_flags = `-crf ${document.getElementById('edit-crf').value} -preset ${document.getElementById('edit-preset').value}`;
target_resolution = document.getElementById('edit-target-resolution').value;
} else {
ffmpeg_flags = `-b:a ${document.getElementById('edit-audio-bitrate').value}`;
}
const data = {
watch_folder: document.getElementById('edit-folder-input').value,
temp_dir: document.getElementById('edit-temp-input').value,
ffmpeg_flags: ffmpeg_flags
ffmpeg_flags: ffmpeg_flags,
target_resolution: target_resolution
};
fetch(`/edit_config/${id}`, {
method: 'POST',
@@ -494,6 +536,7 @@ window.allConfigs = [
watch_folder: {{ config.watch_folder|tojson }},
temp_dir: {{ config.temp_dir|tojson }},
ffmpeg_flags: {{ config.ffmpeg_flags|tojson }},
target_resolution: {{ config.target_resolution|tojson }},
encoder_type: {{ config.encoder_type|tojson }}
},
{% endfor %}