diff options
author | Richard <richard@ha-server.local> | 2016-09-04 11:27:10 +0100 |
---|---|---|
committer | Richard <richard@ha-server.local> | 2016-09-04 11:27:10 +0100 |
commit | db51d3bbfdd63d4c62ca718bfcd7467d2cdc6fa6 (patch) | |
tree | 0a2ada6f32615a2b4f7270b8d2a3e203bff3c1c5 | |
parent | 83632745c43d406856a3b9948cb5ea4a5ec22666 (diff) | |
download | vdr-convert-db51d3bbfdd63d4c62ca718bfcd7467d2cdc6fa6.tar.gz vdr-convert-db51d3bbfdd63d4c62ca718bfcd7467d2cdc6fa6.tar.bz2 |
Files not symlinks
-rw-r--r--[l---------] | FFMPEG patches/FFMPEG-libavformat-reduce-exits-on-subs-error.patch | 20 | ||||
-rw-r--r--[l---------] | VDR patches/VDR2.20-increase-frame-detector-for-x264.patch | 13 | ||||
-rwxr-xr-x[l---------] | VDR patches/VDR2.20-proposal-to-fix-index-generation-for-radio-recording.patch | 91 | ||||
-rwxr-xr-x[l---------] | batch.sh | 59 | ||||
-rwxr-xr-x[l---------] | vdr-auto | 43 | ||||
-rwxr-xr-x[l---------] | vdr-convert | 924 |
6 files changed, 1144 insertions, 6 deletions
diff --git a/FFMPEG patches/FFMPEG-libavformat-reduce-exits-on-subs-error.patch b/FFMPEG patches/FFMPEG-libavformat-reduce-exits-on-subs-error.patch index 00ad9f1..a6fd2f0 120000..100644 --- a/FFMPEG patches/FFMPEG-libavformat-reduce-exits-on-subs-error.patch +++ b/FFMPEG patches/FFMPEG-libavformat-reduce-exits-on-subs-error.patch @@ -1 +1,19 @@ -/opt/data/develop/FFmpeg-rf/FFMPEG-libavformat-reduce-exits-on-subs-error.patch
\ No newline at end of file +--- mux.c 2016-09-01 14:21:01.254840644 +0100 ++++ new/mux.c 2016-09-01 14:18:01.000000000 +0100 +@@ -597,11 +597,11 @@ + pkt->dts = st->pts_buffer[0]; + } + +- if (st->cur_dts && st->cur_dts != AV_NOPTS_VALUE && +- ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && +- st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE && +- st->codecpar->codec_type != AVMEDIA_TYPE_DATA && +- st->cur_dts >= pkt->dts) || st->cur_dts > pkt->dts)) { ++// RF This patch helps prevent ffmpeg exiting unecessarily due to DTS issues in subtitles. ++// My idea is that DTS issues don't matter for subs, they only use PTS. In any case timing not mega critical. ++ ++// if (st->cur_dts && st->cur_dts != AV_NOPTS_VALUE && ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE && st->codecpar->codec_type != AVMEDIA_TYPE_DATA && st->cur_dts >= pkt->dts) || st->cur_dts > pkt->dts)) { ++ if (st->cur_dts && st->cur_dts != AV_NOPTS_VALUE && ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE && st->codecpar->codec_type != AVMEDIA_TYPE_DATA && st->cur_dts >= pkt->dts) || (st->cur_dts > pkt->dts && st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE && st->codecpar->codec_type != AVMEDIA_TYPE_DATA))) { + av_log(s, AV_LOG_ERROR, + "Application provided invalid, non monotonically increasing dts to muxer in stream %d: %s >= %s\n", + st->index, av_ts2str(st->cur_dts), av_ts2str(pkt->dts)); diff --git a/VDR patches/VDR2.20-increase-frame-detector-for-x264.patch b/VDR patches/VDR2.20-increase-frame-detector-for-x264.patch index 4021e2c..8315d28 120000..100644 --- a/VDR patches/VDR2.20-increase-frame-detector-for-x264.patch +++ b/VDR patches/VDR2.20-increase-frame-detector-for-x264.patch @@ -1 +1,12 @@ -/opt/data/develop/vdr/vdr-220/patches/VDR2.20-increase-frame-detector-for-x264.patch
\ No newline at end of file +--- remux.c 2016-09-01 14:53:42.969255952 +0100 ++++ new/remux.c 2016-06-30 17:06:06.000000000 +0100 +@@ -23,7 +23,8 @@ + #define dbgpatpmt(a...) if (DebugPatPmt) fprintf(stderr, a) + #define dbgframes(a...) if (DebugFrames) fprintf(stderr, a) + +-#define MAX_TS_PACKETS_FOR_VIDEO_FRAME_DETECTION 6 ++//RF fix for x264 long commnd line header #define MAX_TS_PACKETS_FOR_VIDEO_FRAME_DETECTION 6 ++#define MAX_TS_PACKETS_FOR_VIDEO_FRAME_DETECTION 10 + #define WRN_TS_PACKETS_FOR_VIDEO_FRAME_DETECTION (MAX_TS_PACKETS_FOR_VIDEO_FRAME_DETECTION / 2) + #define WRN_TS_PACKETS_FOR_FRAME_DETECTOR (MIN_TS_PACKETS_FOR_FRAME_DETECTOR / 2) + diff --git a/VDR patches/VDR2.20-proposal-to-fix-index-generation-for-radio-recording.patch b/VDR patches/VDR2.20-proposal-to-fix-index-generation-for-radio-recording.patch index a516d15..7cdc80e 120000..100755 --- a/VDR patches/VDR2.20-proposal-to-fix-index-generation-for-radio-recording.patch +++ b/VDR patches/VDR2.20-proposal-to-fix-index-generation-for-radio-recording.patch @@ -1 +1,90 @@ -/opt/data/develop/vdr/vdr-220/patches/VDR2.20-proposal-to-fix-index-generation-for-radio-recording.patch
\ No newline at end of file +>From ecfdf422cb4d7e88088cfb740197e684adb69966 Mon Sep 17 00:00:00 2001
+From: Thomas Reufer <thomas@reufer.ch>
+Date: Mon, 13 Jun 2016 16:42:03 +0200
+Subject: [PATCH 9/9] proposal to fix index generation for radio recordings
+
+---
+ recording.c | 8 ++++----
+ remux.c | 4 +++-
+ remux.h | 3 +++
+ 3 files changed, 10 insertions(+), 5 deletions(-)
+
+diff --git a/recording.c b/recording.c
+index a847c7d..b1b7c81 100644
+--- a/recording.c
++++ b/recording.c
+@@ -2324,7 +2324,7 @@ void cIndexFileGenerator::Action(void)
+ Buffer.Del(Processed);
+ }
+ }
+- else if (PatPmtParser.Vpid()) {
++ else if (PatPmtParser.Completed()) {
+ // Step 2 - sync FrameDetector:
+ int Processed = FrameDetector.Analyze(Data, Length);
+ if (Processed > 0) {
+@@ -2346,9 +2346,9 @@ void cIndexFileGenerator::Action(void)
+ PatPmtParser.ParsePmt(p, TS_SIZE);
+ Length -= TS_SIZE;
+ p += TS_SIZE;
+- if (PatPmtParser.Vpid()) {
+- // Found Vpid, so rewind to sync FrameDetector:
+- FrameDetector.SetPid(PatPmtParser.Vpid(), PatPmtParser.Vtype());
++ if (PatPmtParser.Completed()) {
++ // Found pid, so rewind to sync FrameDetector:
++ FrameDetector.SetPid(PatPmtParser.Vpid() ? PatPmtParser.Vpid() : PatPmtParser.Apid(0), PatPmtParser.Vpid() ? PatPmtParser.Vtype() : PatPmtParser.Atype(0));
+ BufferChunks = IFG_BUFFER_SIZE;
+ Rewind = true;
+ break;
+diff --git a/remux.c b/remux.c
+index cfb6ae3..df38add 100644
+--- a/remux.c
++++ b/remux.c
+@@ -603,6 +603,7 @@ cPatPmtParser::cPatPmtParser(bool UpdatePrimaryDevice)
+
+ void cPatPmtParser::Reset(void)
+ {
++ completed = false;
+ pmtSize = 0;
+ patVersion = pmtVersion = -1;
+ pmtPids[0] = 0;
+@@ -893,6 +894,7 @@ void cPatPmtParser::ParsePmt(const uchar *Data, int Length)
+ }
+ }
+ pmtVersion = Pmt.getVersionNumber();
++ completed = true;
+ }
+ else
+ esyslog("ERROR: can't parse PMT");
+@@ -1541,7 +1543,7 @@ void cFrameDetector::SetPid(int Pid, int Type)
+ parser = new cH264Parser;
+ else if (type == 0x24)
+ parser = new cH265Parser;
+- else if (type == 0x04 || type == 0x06) // MPEG audio or AC3 audio
++ else if (type == 0x03 || type == 0x04 || type == 0x06 || type == 0x0F || type == 0x11) // MPEG2,4 AAC, AC3 audio
+ parser = new cAudioParser;
+ else if (type != 0)
+ esyslog("ERROR: unknown stream type %d (PID %d) in frame detector", type, pid);
+diff --git a/remux.h b/remux.h
+index bd0d145..80528bd 100644
+--- a/remux.h
++++ b/remux.h
+@@ -361,6 +361,7 @@ private:
+ uint16_t compositionPageIds[MAXSPIDS];
+ uint16_t ancillaryPageIds[MAXSPIDS];
+ bool updatePrimaryDevice;
++ bool completed;
+ protected:
+ int SectionLength(const uchar *Data, int Length) { return (Length >= 3) ? ((int(Data[1]) & 0x0F) << 8)| Data[2] : 0; }
+ public:
+@@ -397,6 +398,8 @@ public:
+ int Vtype(void) const { return vtype; }
+ ///< Returns the video stream type as defined by the current PMT, or 0 if no video
+ ///< stream type has been detected, yet.
++ bool Completed(void) { return completed; }
++ ///< Returns true if the PMT has been completely parsed
+ const int *Apids(void) const { return apids; }
+ const int *Dpids(void) const { return dpids; }
+ const int *Spids(void) const { return spids; }
+--
+2.4.9
+
@@ -1 +1,58 @@ -/opt/data/develop/scripts/batch.sh
\ No newline at end of file +#!/bin/bash +# Shell script to convert VDR recordings in todo.txt to mpeg4 files. No upload +#------------------------------------------------------------------------------ +# Revision History +#------------------------------------------------------------------------------ +# +# $Log: batch.sh,v $ +# Revision 1.3 2016/09/01 13:08:06 richard +# Pass multiple vdr-convert flags +# Update VDR afterwards +# +# Revision 1.2 2014/12/11 15:24:41 richard +# Improvements +# +# Revision 1.1 2014/12/10 10:44:19 richard +# Initial import - as used in BKK 2014 +# +#------------------------------------------------------------------------------ + +# Usual way to create a "todo" list is as follows: +# find $DIR -type d | grep -i '.rec$' > $DIR/todo.txt + +#Default directory where we work +input='/mnt/lvm/TV' +root=$input + +function usage { + echo "usage: $0 <vdr-convert args>" +} + +function logit() { + logger -s -p local2.warn -t batch "$1" +} + +#------------------------------------------------------------------------------ +# Start of script +#------------------------------------------------------------------------------ + +[ $# -eq 0 ] && usage && exit 1 + +for arg in "$@"; +do + args="$args '$arg'" +done + +# Timeout req'd for troublesome conversions where ffmpeg sometimes get stuck. +# (NOTE Causes issues when run interactively.) +NAMES="$(< $(pwd)/todo.txt)" #names from todo.txt file in this directory +for NAME in $NAMES; do +# /bin/su vdr -c vdr-convert -i "\"$NAME"\" $args" + timeout -k 5h 4h sh -c "vdr-convert -i "\"$NAME"\" $args" + [ $? -ne 0 ] && logit "Fail: problem converting $NAME" +done + +#Ask VDR to re-read the files +touch "$root/.update" + +# --------- $Id: batch.sh,v 1.3 2016/09/01 13:08:06 richard Exp $ ---------- END @@ -1 +1,42 @@ -/opt/data/develop/scripts/vdr-auto
\ No newline at end of file +#!/bin/bash +# Shell script to handle VDR post-recording actions, like noad, H264 conversions +# Based on VDR example +#------------------------------------------------------------------------------ +# Revision History +# +# Removed --asd as it was moving the cut marks to the wrong places +# Removd -C (scene change detection) which seems to mess up marks unecessarily +#------------------------------------------------------------------------------ +# +# $Log: vdr-auto,v $ +# Revision 1.1 2016/09/01 12:35:23 richard +# Initial import +# +#------------------------------------------------------------------------------ + +case "$1" in + before) + echo "Before recording $2" + ;; + started) + echo "Started recording $2" + /usr/local/bin/noad -s /etc/vdr/noadstat.csv -o -c $1 $2 + ;; + after) + echo "After recording $2" + #vdr forks this script, noad forks itself, vdr-convert doesn't + /usr/local/bin/noad -s /etc/vdr/noadstat.csv -o -c $1 $2 + /usr/local/bin/vdr-convert -k -d -i $2 & + ;; + edited) + echo "Edited recording $2" + ;; + deleted) + echo "Deleted recording $2" + ;; + *) + echo "ERROR: unknown state: $1" + ;; +esac + +# --------- $Id: vdr-auto,v 1.1 2016/09/01 12:35:23 richard Exp $ ---------- END
\ No newline at end of file diff --git a/vdr-convert b/vdr-convert index 04a323e..0c6990a 120000..100755 --- a/vdr-convert +++ b/vdr-convert @@ -1 +1,923 @@ -/opt/data/develop/scripts/vdr-convert
\ No newline at end of file +#!/bin/bash + +# VDR-convert Copyright (C) 2014-2016 Richard Farthing + +# A shell script to convert VDR recordings to ts using H264 and AAC + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Note that this script is designed for VDR format 1.x and 2.x recordings only +# +# The aim is to re-create the content as accurately as possible, including all +# streams, audio, AD and subtitles but in more compressed content +# +# For VDR conversion/compatibility we use MPEG Transport Streams (ts). +# Previous versions used Matroska (MKV) for single-use conversions, +# but overly complex to maintain with no huge benefit (marginal size reduction) +# Some commented-out historical lines remain for HandbrakeCLI. +# +# Extract of VDR V1.7.3 history - naming conventions +#The directory name for a recording has been changed from +#YYYY-MM-DD-hh[.:]mm.pr.lt.rec (pr=priority, lt=lifetime) to +#YYYY-MM-DD-hh.mm.ch-ri.rec (ch=channel, ri=resumeId). +#Priority and Lifetime are now stored in the info.vdr file with the new +#tags P and L (if no such file exists, the maximum values are assumed by +#default, which avoids inadvertently deleting a recording if disk space +#is low). No longer storing Priority and Lifetime in the directory name +#avoids starting a new recording if one of these is changed in the timer +#and the recording is re-started for some reason. +#Instead of Priority and Lifetime, the directory name now contains the +#channel number from which the recording was made, and the "resume id" of +#this instance of VDR. +# +#------------------------------------------------------------------------------ +# Compatibility and requirements: +#------------------------------------------------------------------------------ +# VDR 1.x - 2.2x+ recordings in PES or TS format with "info" files +# Obviously script needs write access to VDR recordings (run as vdr?). +# +# Software required +# ----------------- +# Current released ffmpeg in the path (3.x tested) +# Or a patched ffmpeg for damaged (bad reception) recordings or if you want +# the ultimate quality AAC using the non-free libfdk, build ffmpeg with it +# +# For transcoding old VDR1.x recordings to the newer .ts format in H264, and for +# portability of subtitles etc, you need the following also, in your path: +# 1) ISO13818ts from http://www.scara.com/~schirmer/o/mplex13818 (build from source) +# 2) Genindex 0.2 or later (part of this project) +# 3) For auto-uploading to an ftp server: Ncftp. Adavantage of this is it can resume +# in case the upload breaks midway. Auto-resume not always reliable: hence "redo" opt +# +# The script works around various issues with existing VDR recordings +# esp. packet timings, PTS and DTS timestamp values. However if your recordings +# are really broken, you are first likely to see failures in the subtitle tests (map fn.) +# You can either: +# a) use -n flag and discard subs, OR +# b) Patch ffmpeg as specified in the README, and let ffmpeg work through. +# Set the ffmpeg variable below to point to your patched version +# In reality this is often produces perfect results, inc subs. +# +#------------------------------------------------------------------------------ +# Revision History +#------------------------------------------------------------------------------ +# +# $Log: vdr-convert,v $ +# Revision 1.3 2016/09/01 13:01:48 richard +# Added support for VDR1.x files +# Added extensive support for dvb subtitles +# Added options c, d, e, l, m, q, t, y +# Added in-place conversion for "keeping" (-k) +# Added tests for size, duration, indexing +# Added workarounds for poor recordings/ffmpeg +# Added lockfile to prevent multiple invocations +# All streams labelled using data from info(.vdr) file +# Stream re-ordering to accomodate typ. players +# Extensive testing on 8yr libary of recordings +# Removed most mkv/mkvmerge/HandbrakeCLI code +# +# Revision 1.2 2014/12/12 12:35:31 richard +# Support VDR2 and ts files +# Support and tag HD/SD +# Fix audio encoding parameters +# Code tidy +# +# Revision 1.1 2014/12/10 10:44:19 richard +# Initial import - as used in BKK 2014 +#------------------------------------------------------------------------------ + +# ********************************* TODO >> ********************************** +# +# Logging criticality - all same atm +# Paths for executables, used as variables, test for their existance +# mplayer plays at 2x speed (confuses fps, -fps 25 doesn't help) +# Automatic test/retry FTP upload size to fix upload failures +# +# ffmpeg bug (for VDR1.x subs) https://trac.ffmpeg.org/ticket/4855 +# FFmpeg bug (early exit on all types of recordings) https://trac.ffmpeg.org/ticket/5617 +# FFmpeg bug (some subtitles not handled) https://trac.ffmpeg.org/ticket/5796 +# +# ********************************* << TODO *********************************** + +# CONSTANTS / USER CONFIG + +#This is for detailed ffmpeg output +LOGFILE="/var/log/vdr-convert.log" +#ffmpeg="timeout -k 5h 4h nice -n 19 /opt/data/develop/FFmpeg/ffmpeg -y -hide_banner -nostats -probesize 250M -analyzeduration 600M -copytb 1" +# Timeout req'd for troublesome conversions where ffmpeg can (rarely) get stuck. However causes issues when run interactively. +ffmpeg="nice -n 19 /opt/data/develop/FFmpeg/ffmpeg -y -hide_banner -nostats -probesize 250M -analyzeduration 600M -copytb 1" + +#Minimum file we bother to convert +#Note esp. that small mkv files often fail to concatenate in mkvmerge +minsize='2000000' + +# libfdk is supposed to be better but seems to produce larger files and +# doesn't subjectively (to me) sound better. It's a bit faster to encode +aaccodec='aac' # aac or libfdk_aac if ffmpeg distribution / built GPL + +minsubsize=250 # kb, a subs stream smaller than this probably broken or just a few in the ads + # Also broadcaster "cleardown" - essentially empty - packets contribute + # This is a default, recording duration is used to calculate more accurately + +# Defaults in case not provided by info file +Alangstereo="eng" +Alangmono="eng" +Alangmulti="eng" +Alangsurround="eng" +ADlang="eng" +Slang="eng" +ext="ts" # target file extension + +# ********************* Should not need to edit below here ******************** + +lifetime=99 +meg=1048576 +# Catch piped errors +set -o pipefail +# New line +IFS=$'\n' + +#------------------------------------------------------------------------------ +# FUNCTIONS +#------------------------------------------------------------------------------ + +function quit() { + rm -f "$LOCKFILE" || { logit "failed to delete $LOCKFILE"; exit 1; } + exit $1 +} + +#------------------------------------------------------------------------------ +function logit() { + logger -s -p local2.warn -t vdr-convert "$1" +} + +#------------------------------------------------------------------------------ +# Must analyse separately as wrong duration returned once -copyts used (for subs) + +function progduration { + if [ $debug -eq 1 ]; then + duration=$(echo $debug_time|cut -f2 -d"t" | awk -F: '{ printf "%0.f", ($1 * 3600) + ($2 * 60) + $3 }') + else + duration=$(eval "$ffmpeg -i "\"$1"\" 2>&1" |grep -i "duration:" |cut -f2-5 -d":" | awk -F: '{ printf "%0.f", ($1 * 3600) + ($2 * 60) + $3 }') + fi + if [ $duration ]; then + echo;echo "Duration is $duration seconds" + minsubsize=$(($duration / 9)) # 400k / hr absolute min (empirical) + fi +} + +#------------------------------------------------------------------------------ +# Analyse streams +# Handbrake starts at 1. We take the first / best stream, ac3 then stereo etc +# (we assume only 1 present) +# Handbrake is variable around audio channels +# cant ask for 1,2 unless both there as it barfs, (or did do) + +# Concatenating AAC at stream level has resulted in lipsync issues, do file level +#------------------------------------------------------------------------------ + +function map() { +# $1 is filename, $2 is filename count + + declare -a VIDEO='()' + declare -a AUDIO='()' + declare -a SUBS='()' + F_VIDEO="" + F_AUDIO="" + F_SUBS="" + H_AUDIO_MAP="" + H_AUDIO_CODEC="" + H_AUDIO_QUALITY="" + H_SUBS="" + h264=0 + aac=0 + +# Multichannel recordings often start in stereo, e.g. in a continuity announcement +# This trick gets the ONE "best" audio stream say 5 mins in to check what it really is +# ffmpeg -loglevel quiet -ss 00:05:00 -i "00001.ts" -t 10 -c copy -vn -avoid_negative_ts 1 -f nut pipe:1 | ffmpeg -hide_banner -i pipe:0 +# Also AD steam contents often missing in continuity (empty). +# So a) check after 5 mins for 5.1, and b) probe well into the file for streams not yet running - like AD +# bestaudio=`ffmpeg -loglevel quiet -probesize 200M -analyzeduration 200M -ss 00:05:00 -i "$1" -t 10 -c copy -vn -avoid_negative_ts 1 -f nut pipe:1 | ffmpeg -hide_banner -i pipe:0 2>&1 |grep -i "stream #0:"` +# echo "ffmpeg best audio is:" +# echo "$bestaudio" + + mapst="" + echo "ffmpeg stream data:" + streams=$(eval "$ffmpeg -i "\"$1"\" 2>&1" |grep -i "stream #0:") # Line per stream + echo "$streams" + for line in $streams; + do + stream_id=$(echo $line |cut -f2 -d":" |cut -f1 -d"[" |sed 's/[^0-9]*//g') + bitrate=$(echo ${line##*,} | sed 's/[^0-9]*//g') + ! [ $(echo $bitrate | sed 's/[^0-9]*//g') ] && bitrate=0 # always need a number + if [ $(echo $line |grep -i "video") ]; then + mapst="$mapst A:$((10000 - $bitrate)):$stream_id" # The 'A' character & bitrate is used as a score for sorting streams later + + [ $(echo $line |grep -i "h264") ] && h264=1 + + # Notes on ffmpeg video parameters: + # --------------------------------- + # Assumption: anything worth playing video today will handle H264 high profile 4.0, including Raspberry Pi. + # (also Freeview HD mandates it, making it a pre-requisite) + + # yadif/bwdif deinterlace option (if interlaced) as we see interlacing crap on fast movements using Kodi or VLC + # NOTE that yadif/bwdif outputs 1 frame per *field* (send_field=1) = 50fps PAL when set to 1. + # Larger file but can be better esp. on mixed old/new material. Or just leave interlaced and let player deinterlace + + # Crop option is to remove overscan crap that the broadcaster forgot to filter at the top of the picture + # (2-4 pixels often, +check for MPEG artifacts adjacent left over). + + # -g (max keyframe gap) adjusted to improve skipping which is poor at the ffmpeg default 250. + # -g 75 Adds about 2% to filesize, -g 50 adds ~3.5%. 12 is common in broadcast! + # (See http://www.lighterra.com/papers/videoencodingh264) + + # Can also transcode to H265, but v. slow (<real time on dual core) and only about 20% space savings over H264, + # + RPi v1 cannot handle + + [ $top -gt 0 ] && crop="-vf crop=in_w:in_h-$top:0:$top" + if [ $dif ]; then + flags="+loop" + deint="-vf yadif=$dif:-1:0" + else + flags="+loop+ilme+ildct" + fi + VIDEO[$stream_id]="-c:v:stream_op libx264 -preset $preset -profile:v high -level 4.0 -crf $quality -g 50 -flags $flags $extras $deint $crop" + # No deinterlace normally req'd. + [ $mpeg -eq 1 ] && VIDEO[$stream_id]="-c:v:stream_op copy -g 50 $extras $deint $crop" +# v. quick functional testing only +# F_VIDEO="-c:v libx264 -preset ultrafast -vf scale=112:63" +# F_VIDEO="-c:v mpeg2video" + # HandBrake params are less tested as it is no longer the chosen encoder. Added decomb for interlacing issues + H_VIDEO="-e x264 --decomb --crop $top:0:0:0 --strict-anamorphic -m -x ref=1:weightp=1:subq=2:rc-lookahead=20:trellis=0:8x8dct=0 -q $quality" + fi + if [ $(echo $line |grep -i "audio") ] && [ $mpeg -eq 1 ]; then + AUDIO[$stream_id]="-c:a:stream_op copy -metadata:s:a:stream_op language=$Alangstereo" + mapst="$mapst B:$((10000 - $bitrate)):$stream_id" + H_AUDIO_MAP="$H_AUDIO_MAP $stream_id," + H_AUDIO_CODEC="$H_AUDIO_CODEC copy:*," + # Multichannel audio - just copy to best preserve. Can be AAC 5.1. Always put multichannel ahead of others + elif [ $(echo $line |grep -i "audio" |grep -i "ac3\|5.1") ]; then + mapst="$mapst C:$((10000 - $bitrate)):$stream_id" + H_AUDIO_MAP="$H_AUDIO_MAP $stream_id," + H_AUDIO_CODEC="$H_AUDIO_CODEC copy:*," + AUDIO[$stream_id]="-c:a:stream_op copy -metadata:s:a:stream_op language=$Alangsurround" + # AAC best left as is. We can't always detect AAC 5.1, so just copy AAC as well + elif [ $(echo $line |grep -i "audio" |grep -i "aac") ]; then +# H_AUDIO="-a $STREAM -E av_aac -B $multibitrate" + mapst="$mapst E:$((10000 - $bitrate)):$stream_id" + H_AUDIO_MAP="$H_AUDIO_MAP $stream_id," + H_AUDIO_CODEC="$H_AUDIO_CODEC copy:*," + AUDIO[$stream_id]="-c:a:stream_op copy -metadata:s:a:stream_op language=$Alangstereo" + aac=1 + else + # MP2 usually. ffmpeg sometimes says "2 channels". + # AD can actually be stereo, esp in HD, or "0 channels" - a null stream, which we drop + if [ $(echo $line |grep -i "audio" |grep -i "stereo\|2 channels") ]; then + # sometimes the bitrate is already lower than our target, like 128k! AAC good at low rates anyway + outputbitrate=$stereobitrate + mapst="$mapst G:$((10000 - $bitrate)):$stream_id" + (( bitrate = bitrate * 10 )) # AKA 192k -> 160k + (( bitrate = bitrate / 12 )) + if [ $outputbitrate -gt $bitrate ]; then + outputbitrate=$bitrate + [ $outputbitrate -lt 100 ] && logit "Fail: Audio bitrate too low ($outputbitrate), exiting" && quit 1 + fi + H_AUDIO_MAP="$H_AUDIO_MAP $stream_id," + H_AUDIO_CODEC="$H_AUDIO_CODEC mp3," + H_AUDIO_QUALITY="$H_AUDIO_QUALITY $stereoquality," +# AAC now reported as OK CBR, Dec 2015. Smaller files too (than mp3) + AUDIO[$stream_id]="-c:a:stream_op $aaccodec -b:a:stream_op $outputbitrate"k" -metadata:s:a:stream_op language=$Alangstereo" + elif [ $(echo $line |grep -i "audio" |grep -i "mono") ]; then + # Likely the AD stream, at low bitrate. + # !!!AAC good at low bitrates. But sparse audio gives Kodi/VLC etc trouble, as does MP3 - so copy MP2: 0.5% hit approx! + mapst="$mapst J:$((10000 - $bitrate)):$stream_id" + H_AUDIO_MAP="$H_AUDIO_MAP $stream_id," + H_AUDIO_CODEC="$H_AUDIO_CODEC mp3," + # can't easily mix quality and bitrate in Handbrake cli, so use stereo + H_AUDIO_QUALITY="$H_AUDIO_QUALITY $stereoquality," + AUDIO[$stream_id]="-c:a:stream_op copy -metadata:s:a:stream_op language=$ADlang" + fi + fi + if [ $subs -eq 1 ] && [ $(echo $line |grep -i "dvb_subtitle") ]; then + # Broken subs streams WILL cause an early exit, so test them quickly : both ability to copy & their size + # (Skip any dvd_sub misidentified in old VDR recordings - e.g. if .vdr files passed unprocessed) + # Can be multiple streams. + echo;echo "Testing subtitles for problems..." + substest=$(eval "$ffmpeg -i "\"$1"\" -vn -an -dn -c:s copy -f mpegts /dev/null 2>&1") + [ $? -ne 0 ] && logit "ffmpeg failed subtitles stream test" && quit 1 + subslen=$(echo "$substest" |grep "subtitle:"|cut -f4 -d":"|sed 's/[^0-9]*//g') + # arbitrary size check, but generally produces little if broken + if [ $subslen -ge $minsubsize ]; then + echo "Subs length $subslen kbytes OK" + mapst="$mapst L:10000:$stream_id" + H_SUBS="$H_SUBS -s $stream_id" + SUBS[$stream_id]="-c:s:stream_op copy -metadata:s:s:stream_op language=$Slang" + # Unsure if forcing dvbsub causes trouble. Usually unhelpful: results in different size o/p + # SUBS[$stream_id]="-c:s:stream_op dvbsub -metadata:s:s:stream_op language=$Slang" + else + logit "Warning ffmpeg failed subtitles stream in $1: too small ($subslen kb), none or broken: no subtitles output" + # If VDR2, perhaps not possible to transcode with subs. VDR1.x size already assessed, shouldn't get here + [ ! $VDRtype ] && [ $delete -eq 1 ] && delete=0 && logit "No longer deleting originals for $1 due to suspect subtitle stream size" + fi + fi + done + + # Handbrake needs parameters listed like this - per stream + H_AUDIO="-a $H_AUDIO_MAP -E $H_AUDIO_CODEC -Q $H_AUDIO_QUALITY" + + # Order streams by initial alpha AND bitrate to fix stream order change by iso13818ts, or random broadcaster ordering + # (many players play the first audio stream without user intervention). Produces "best" audio first, a bit like ffmpeg + # Note that stream ordering (as reported by ffmpeg) has been observed varying between recording files in same recording! + [ $debug -eq 1 ] && echo;echo "Raw map string is: $mapst" + saveifs=$IFS + IFS=' ' + totalmap=$(echo "$mapst"|tr " " "\n"|sort|cut -f1,3 -d":"|tr "\n" " "|tr "[:alpha:]" "$2") + mapping="" + vcount=0; acount=0; scount=0 + + # Build the OUTPUT maps (order of outputs) + for m in $totalmap; + do + mapping="$mapping -map $m" + id=$(echo $m|sed -e 's/[0-9]://g') + [ "${VIDEO[$id]}" ] && { F_VIDEO="$F_VIDEO $(echo "${VIDEO[$id]}" |sed -e "s/stream_op/$vcount/g")";vcount=$(($vcount+1));} + [ "${AUDIO[$id]}" ] && { F_AUDIO="$F_AUDIO $(echo "${AUDIO[$id]}" |sed -e "s/stream_op/$acount/g")";acount=$(($acount+1));} + #iff subs (enabled and actually present) we need -copyts for proper sync + [ "${SUBS[$id]}" ] && { F_SUBS="$F_SUBS -copyts $(echo "${SUBS[$id]}" |sed -e "s/stream_op/$scount/g")";scount=$(($scount+1));} + done + IFS=$saveifs + + map="$map $mapping" # Additive complete map where needed + echo;echo "stream mapping for $1 is:" + echo "$map" + [ $debug -eq 1 ] && echo "$F_VIDEO $F_AUDIO $F_SUBS" + return 0 +} + +#------------------------------------------------------------------------------ + +function convertvdr() { + # $1 is input filename, $2 is output path/filename + # returns 0 on success, 1 on fail, 2 if already in H264/AAC + # Note always need to probe as deeply else ffmpeg can miss/fail, e.g AD streams/subs + main=$1 + tempdir=$(echo "$(dirname "$1")""/temp") + + if [ $VDRtype ]; then + # The tricky bit - getting old VDR 1.x files into a format that ffmpeg can transcode, esp. non-std dvbsubs + # This preprocessing is v. quick, really only limited by disk accesss time, as it's just stream copying & manipulation + # -copyts is CRITICAL to getting the timing of the final subs streams aligned properly, as opposed to approximately! + # genindex always creates 001.vdr + index.vdr + + # NOTE an earlier version made the ts's (disk intensive) only when subs detected by genindex + # but ffmpeg occasionally fails detection of *any* streams when given an unprocessed .vdr file + rm -f "$tempdir"/*.vdr # in case we're re-doing: genindex otherwise fails + mkdir -p -- "$tempdir" + [ $? -ne 0 ] && logit "Failed - cannot create working directory $tempdir" && return 1 + subxsize=$(genindex -n -b -i "$1" -d "$tempdir" |grep -i "^PES: Stats" |grep -i "subtitle data" |cut -f2 -d":"|sed 's/[^0-9]*//g') + if [ $? -ne 0 ]; then + logit "Fail: genindex problem converting $1" + [ $debug -eq 0 ] && rm -rf "$tempdir" || logit "could not remove $tempdir" + return 1 + else + echo + logit "$1 genindex extracted $subxsize kb subtitle data" + # convert to ffmpeg-compatible .ts (so ffmpeg can see the streams in an mpegts container - no dvb decoder for PS/PES) + # NOTE that iso13818ts oftens changes the order of streams which may be relevant for audio playback - see map fn. + # Fix this later when reassembling streams + tmpts="$tempdir/001.ts" + nice -n 19 iso13818ts -P "$tempdir/001.vdr" 1 > "$tmpts" + if [ $? -ne 0 ]; then + logit "Fail: Problem converting $tempdir/001.vdr to mpegts, deleting $tmpts !" + [ $debug -eq 0 ] && rm -f $tmpts || logit "could not remove $tmpts" + return 1 + fi + [ $debug -eq 0 ] && rm -f "$tempdir"/*.vdr || logit "could not remove temp files" # No longer reqd + + progduration $tmpts + program_duration=$(($program_duration + $duration)) # combine all files. Must test as .ts, .vdr can fail + + # Subs stream + if [ $subs -eq 1 ]; then + if [ $subxsize -gt $minsubsize ]; then + # Subs streams: MUST DO SEPARATELY as ffmpeg has big problems extracting ALL the subs and AD sparse packets + # (cannot currently convert a small filtered subs-only file - doesn't work) + # Many players also have this problem - esp if the video stream is present. + # After exhaustive testing with many flags / options, atm there's NO other way to avoid this and reliably extract ALL dvbsubs + subsfile="$tempdir/subs.ts" + cmd="$ffmpeg -copyts -i "\"$tmpts"\" -c:s copy -vn -an -dn "\"$subsfile"\"" + sh -c $cmd + if [ $? -ne 0 ]; then + logit "Fail: ffmpeg problem extracting subs stream(s) of $tmpts, deleting $subsfile (check $LOGFILE)" + [ $debug -eq 0 ] && rm -f "$subsfile" || logit "could not remove $subsfile" + return 1 + fi + + # add and map the extra subs file, so the main conversion catches it, check ffmpeg didn't drop more than 10kb subs (~1 sub) + map $subsfile 1 + [ $subxsize -gt $(($subslen+10)) ] && logit "Fail: ffmpeg did not process all subs for $1 (VDR 1.x, $subslen kb processed, $subxsize kb extracted), deleting $tempdir !" && rm -rf "$tempdir" && return 1 + # ffmpeg used to report EXACTLY the same size, newer versions perhaps not + [ $subxsize -ne $subslen ] && logit "Warning: ffmpeg reported different subs size to genindex (VDR 1.x) for $1 ($subslen kb vs. $subxsize kb)" + map="-i \"$subsfile\" $F_SUBS $map" + echo;echo "Final subs map is $map" + echo + else + logit "Warning: only $subxsize kb subtitles in $1 (VDR 1.x), expected min. $minsubsize kb: none/broken/only in ads: none converted" + fi + else + logit "Skipping subtitles in $1 per command" + fi + + # Main AV stream(s). Lose any subs here, as they won't (all) copy in ONE go, nice as that would be! + # Also required to ensure streams sync properly when combined. + # Need copyts on main streams to sync subs when present. Can cause ffmpeg to exit early if no subs or DTS issues + copyts="" + [ "$F_SUBS" ] && copyts="-copyts" + main="$tempdir/main.ts" + cmd="$ffmpeg $copyts -i "\"$tmpts"\" -c:v copy -c:a copy -sn -dn -map 0 "\"$main"\"" + sh -c $cmd + if [ $? -ne 0 ]; then + logit "Fail: ffmpeg problem extracting main streams of $main (VDR 1.x), deleting (check $LOGFILE)" + [ $debug -eq 0 ] && rm -f $main || logit "could not remove $main" + return 1 + fi + [ $debug -eq 0 ] && rm -f "$tmpts" + fi + #end VDR1.x + else + # VDR2 + progduration $main + program_duration=$(($program_duration + $duration)) # combine all files. + fi + + # The main conversion + map $main 0 # Additive map for VDR1.x conversions (add in subs file/map as reqd) + reportedvideo=$F_VIDEO + reportedaudio=$F_AUDIO + + if [ "$F_AUDIO" ]; then # Only if we see something meaningful (may be only audio if radio recording) + if [ $keep -eq 1 ]; then + # might want to compress more for temp use, but never if keeping + [ $h264 -eq 1 -o $aac -eq 1 ] && logit "Skipping $main: already H264 or AAC" && return 2 + # LATM multiplexing matches broadcast MPEG4 HD audio streams, but can't currently be demuxed to plain AAC by FFMPEG + # So even if keeping (e.g. automatic VDR transcode), without video use plain AAC so can transcode again if req'd + [ "$F_VIDEO" ] && [ $mpeg -eq 0 ] && latm="-mpegts_flags latm" + fi + #copyts is already in the map if/when req'd + cmd="$ffmpeg $debug_time -i "\"$main"\" $map $F_VIDEO $F_AUDIO $F_SUBS -f mpegts $latm "\"$2"\" 2>> $LOGFILE" + echo + logit "Executing $cmd" + sh -c $cmd + if [ $? -ne 0 ]; then + logit "Fail: ffmpeg problem converting $input, deleting "\"$2"\" !" + rm -f "$2" || logit "could not remove "\"$2"\"" + return 1 + fi + # clean up + else + logit "Fail: Nothing to convert for $input !" + return 1 + fi + if [ $VDRtype ]; then + [ $debug -eq 0 ] && rm -rf "$tempdir" || logit "could not remove $tempdir" + fi + map="" # clear in case we process more files + return 0 +} + +#------------------------------------------------------------------------------ + +function upload() { + # Upload to an ftp server for faster remote download. + # Note that ncftp is not always reliable, seems to randomly quit. + # Ideally need to a size compare & retry function + # NCFTP debug logs don't really help + logit "Uploading $1" + sh -c "ncftpput -z -t 99 -u \"$user\" -p \"$pass\" \"$server\" \"$ftppath\" \"$1\" &" +} + +function usage() { + echo "usage: $0 --input-path | -i \"<recording.rec>\" [--keep |-k] [--combine |-c] [--delete |-d] [--extras |-e \"<ffmpeg opts>\"] [--nosubs] |-n] [--life |-l <days>] [--mpeg |-m] |--quality |-q <CRF>] [--yadif |-y <0|1>] [--tsonly |-t] [--crop-top <lines>] [--ftp] [--ftp-path <target path>] [--redo] |-r ] [--server |-s] [--user |-u] [--pass |-p]" +} + +function escape() { + sed 's/'"'"'/'"\'"'/g' +} + +function cleanup() { + # logit "Deleting intermediate file(s)" + cmd="rm -f $TMPFILES" + [ $debug -eq 0 ] && sh -c $cmd || logit "could not remove file(s)" +} + +#------------------------------------------------------------------------------ +# Start of script +#------------------------------------------------------------------------------ + +# Default parameters. Most can be overridden by command line options + +ftp=0 # FTP out the transcoded file +redo=0 # resend named file by FTP +keep=0 # save as named file or replace recording with compressed version +subs=1 # Subs cause us 95% of problems. Option to drop them. See ffmpeg bug #5617 +life=8 # If recording lifetime >= this, we compress if keeping, otherwise skip to save time & energy +ts=0 +delete=0 # safety - by default we don't delete originals, even if nominally "keeping" them +dif="" # deinterlacer mode: 0=send_frame (25 fps), 1=send_field (50 fps). + # 15-25% file size increase but also a bit better on old/noisy analogue material +debug=0 # In debug mode, only short conversions are done, and intermediate files kept +debug_time="" +combine=0 # concatenate files before conversion. Limit is a total 16GB (in Genindex) +duration=0 + +# Default conversion parameters +# Tuned to give balance of reasonable size and quality - for single use +stereoquality=4 +stereobitrate=128 +#aac, 5.1 etc, BBC AAC output reported as 200 +#Note due to issues around nonfree AAC encoders and need to specially compile +#ffmpeg, now just copy the audio stream if multi-channel +multibitrate=192 +#likely AD stream. CBR is best for this +monobitrate=50 +#Video - see https://trac.handbrake.fr/wiki/ConstantQuality +#default, use higher for kept files. (20-21 good as long as you use medium or slower x264 preset) +quality=23 +top=0 # optional cropping +mpeg=0 +preset="faster" + +while [ "$1" != "" ]; do + case $1 in + -i | --input ) shift + input="$1" + ;; + --debug ) debug=1 + debug_time="-t 00:01:00" + ;; + --crop-top ) shift + top="$1" + ;; + -c | --combine ) combine=1 + ;; + -d | --delete ) delete=1 + ;; + -e | --extras ) shift + extras="$1" + ;; + -k | --keep ) keep=1 + quality=20.5 + stereoquality=3 + stereobitrate=160 + preset="medium" # better actual quality at same CRF. Can go slower but unconvinced of benefit + ;; + -l | --life ) shift + life=$(echo $1|sed -e 's/[^0-9]//g') + ;; + -m | --mpeg2 ) mpeg=1 + ;; + -n | --nosubs ) subs=0 + ;; + -q | --quality ) shift + quality=$(echo $1|sed -e 's/[^0-9]//g') + ;; + -y | --dif ) shift + dif=$(echo $1|sed -e 's/[^0-9]//g') + ;; + -t | --tsonly ) ts=1 + ;; + --ftp ) ftp=1 + ;; + -r | --redo ) redo=1 + ;; + --ftp-path ) shift + ftppath="$1" + ;; + -s | --server ) shift + server="$1" + ;; + -u | --user ) shift + user="$1" + ;; + -p | --pass ) shift + pass="$1" + ;; + -h | --help ) usage + exit 0 + ;; + * ) usage + exit 1 + esac + shift +done + +[ ! -d "$input" ] && logit "Recording directory '"$input"' doesn't exist!" && usage && exit 0 +[ $dif ] && [ $dif -gt 1 ] && logit "deinterlacer config must be 0 or 1" && exit 1 + +# VDR has been known to "start/stop" multiple times at the end of a recording (VPS?) - so prevent multiple instances +LOCKFILE="$input/vdr-convert.pid" + +# check for existing lockfile +if [ -e "$LOCKFILE" ]; then + # lockfile exists + [ -r "$LOCKFILE" ] || { logit "Error: cannot read lockfile"; exit 1; } + PID=$(cat "$LOCKFILE") + kill -0 "$(cat "$LOCKFILE")" 2>/dev/null && { logit "Error: existing instance of $0 is already running, exiting"; exit 1; } + # process that created lockfile is no longer running - delete lockfile + rm -f "$LOCKFILE" || { logit "Error: failed to delete $LOCKFILE"; exit 1; } +fi + +# create lockfile +echo $$ >"$LOCKFILE" || { logit "Error: cannot create lockfile for $0"; exit 1; } + +logit "Processing recording "\"$input"\", keep=$keep, delete=$delete, combine=$combine, subs=$subs, extras=$extras" + +# VDR filetypes 1.x (PES) or 2.x (TS), based on filenames in recording directory + +#vdrfiles=(`find "$1" -type f -name '*.vdr' | wc -l`) +#tsfiles=(`find "$1" -type f -name '*.ts' | wc -l`) +vdrfiles=0; tsfiles=0 +vdrfiles=($(ls -1 "$input" | grep ".vdr$" |grep -o '[0-9]\{3\}'| wc -l)) +tsfiles=($(ls -1 "$input" | grep ".ts$" |grep -o '[0-9]\{5\}'| wc -l)) +# must have 1 data file min, ideally an info file. We don't care about index file here +[ $vdrfiles -lt 1 -a $tsfiles -lt 1 ] && logit "vdr=$vdrfiles,ts=$tsfiles : too few recording / info files!" && quit 1 + +# Assume there are multiple files, as merging is v. quick +if [ $vdrfiles -gt 0 ]; then + VDRtype=".vdr" + FILES="$input/0*.vdr" +else + VDRtype="" + FILES="$input/0*.ts" +fi +#count=(`echo $FILES | wc -w`) + +[ $VDRtype ] && [ $ts -eq 1 ] && logit "Skipping $input, t flag and not a .ts" && quit 0 + +# Get all relevant metadata out of the info(.vdr) file, if it exists +[ -e "$input/info$VDRtype" ] && meta=$(cat "$input/info$VDRtype") + +TITLE="NO TITLE" +[ "$meta" ] && for line in "$meta"; +do + #naming + TITLE=$(echo "$line" |grep "^T" |sed -e "s/^T //") + # optional fields - add them for improved ID + ID[0]=$(echo "$line" |grep "^S" |sed -e "s/^S //") #subtitle + EP=$(echo "$line" |grep "^D" |grep -oP '(?<=Episode:).*' |cut -f1 -d"|") + # Meta info event date, time and DOW, 3rd field - useful in file listings for offline use + ID[2]=$(echo "$line" |grep "^E" |cut -f3 -d" " |awk '{print strftime("%F-%a_%H-%M",$1)}') + + #resolution. Just look for HD in station name - a bit simplistic, but only used in naming + HD=$(echo "$line" |grep "^C" | cut -f3-7 -d" "| grep -o 'HD') + + #Stream metatdata also. Video we don't check - no lang usually associated + X201=$(echo "$line" |grep "^X 2 01\|^X 2 02" |cut -f4 -d" ") + X203=$(echo "$line" |grep "^X 2 03" |cut -f4 -d" ") + X204=$(echo "$line" |grep "^X 2 04" |cut -f4 -d" ") + X205=$(echo "$line" |grep "^X 2 05" |cut -f4 -d" ") + X240=$(echo "$line"|grep "^X 2 40" |cut -f4 -d" ") + X31=$(echo "$line" |grep "^X 3 1" |cut -f4 -d" ") + [ !$VDRtype ] && lifetime=$(echo "$line" |grep "^L" |sed 's/[^0-9]*//g') +done + +# There are often multiples, take the first - naff, I know +IFS=' ' read -ra X201 <<<"$X201" +[ $X201 ] && Alangmono=$X201 +IFS=' ' read -ra X203 <<<"$X203" +[ $X203 ] && Alangstereo=$X203 +IFS=' ' read -ra X204 <<<"$X204" +[ $X204 ] && Alangmulti=$X204 +IFS=' ' read -ra X205 <<<"$X205" +[ $X205 ] && Alangsurround=$X205 +IFS=' ' read -ra X240 <<<"$X240" +[ $X240 ] && ADlang=$X240 +IFS=' ' read -ra X31 <<<"$X31" +[ $X31 ] && Slang=$X31 + +[ "$EP" ] && ID[1]=EPISODE$EP + +for token in ${ID[@]} +do + [ "$token" ] && TITLE=$(echo "$TITLE-$token") +done + +if [ $VDRtype ]; then + ROOTDIR=$(echo $(dirname "$input")) + BASEDIR=$(echo $(basename "$input")) + priority=$(echo $BASEDIR | cut -f4 -d".") + lifetime=$(echo $BASEDIR | cut -f5 -d".") +fi + +# Short lifetime files skipped if we are "keeping" them - save time & energy +[ $lifetime -lt $life ] && [ $keep -eq 1 ] && logit "Skipping $input, recording lifetime is $lifetime" && quit 0 + +# Option to combine input files: Useful if there are lots of very small fragments due to interruptions. +# Usually we convert separately, in case a broken file fails. Combining might be less reliable, +# esp. with lots of interruptions, ffmpeg duration estimations can often get messed up +if [ $combine -eq 1 ] && [ $vdrfiles -gt 1 -o $tsfiles -gt 1 ] && [ $redo -ne 1 ]; then + cat $FILES > "$input/recording.orig" + if [ $? -eq 0 ]; then + FILES="$input/recording.orig" + else + logit "Failed to combine input files for $input, trying individually" && rm -f "$input/recording.orig" + fi +fi + +# Conversion +for f in $FILES +do + size=$(stat -c %s "$f" ) + if [ $size -gt $minsize ]; then + totalsize=$(($totalsize + $size)) + fileno=$(($fileno + 1)) + echo + logit "Processing $f file $fileno, size $(($size/$meg))M..." + TMPFILE=$(echo "$TITLE-file-$fileno" | tr -s "\/:?*<>| " '-') + OUTFILE="$TMPFILE.$ext" + if [ $keep -eq 1 ]; then + OUTFILE="$input/$OUTFILE" + else + OUTFILE="$(pwd)/$OUTFILE" # default to current + fi + TMPFILES=$(echo "$TMPFILES \"$OUTFILE"\") + # horrible but mkvmerge doesn't like +file +file + if [ $fileno -eq 1 ]; then + MERGEFILES=$OUTFILE + else + MERGEFILES=$(echo "$MERGEFILES|$OUTFILE") +# HandbrakeCLI +# MERGEFILES=`echo "$MERGEFILES +$OUTFILE"` +# # ffmpeg with mkv - not so good +# MERGEFILES=`echo "$MERGEFILES|$OUTFILE"` + fi + if [ $redo -eq 0 ]; then + convertvdr "$f" "$OUTFILE" + return=$? + if [ $debug -eq 0 ] && [ $combine -eq 1 ] && [ -e "$input/recording.orig" ]; then + rm -f "$input/recording.orig" || logit "could not remove $input/recording.orig" + fi + [ $return -eq 1 ] && quit 1 + [ $return -eq 2 ] && quit 0 + fi + else + logit "Skipping $f - too small ($(($size/$meg))M). Try combining input files with -c option" + # !!!continue but no longer delete (so don't lose anything if it goes wrong) + [ $keep -eq 1 ] && [ $delete -eq 1 ] && delete=0 && logit "No longer deleting files for $input" + fi +done + + +# Map the last output file +# Why? Sometimes a stream actually has no content (e.g. AD or subs), +# even if the stream notionally existed when probed, ffmpeg usually drops it silently if empty, +# resulting in a different map in the output file +# ffmpeg still needs a specific map to copy more than just "the best" single audio & video stream. +# For VDR1.x conversions, this also fixes the output stream mapping order (audio and subs first -> subs last) +# Note map of each file SHOULD be the same, or there will be trouble. + +map $OUTFILE 0 + +# SD or HD?. Enhanced EPG would tell us - but not always present +if [ $F_VIDEO ]; then + DEF=$HD + [ ! $DEF ] && DEF="SD" +else + [ $F_AUDIO ] && DEF="AUDIO" +fi + +# Now merge them, avoid faffing about with separate files +NAME="$TITLE-$DEF" +BASENAME=$(echo "$TITLE" | tr -s "\/:?*<>| " '-') +if [ $keep -eq 1 ]; then + OUTFILE="$input/$BASENAME.$ext" +else + # On a "redo" can't test for type, so only use basename when ftp'ing + [ $ftp -eq 0 -a $redo -eq 0 ] && BASENAME="$BASENAME-$DEF" + OUTFILE="$(pwd)/$BASENAME.$ext" # default to current directory + AUDIO_ONLY="$(pwd)/$BASENAME.m4a" +fi + +if [[ $redo -eq 0 && $MERGEFILES ]]; then + #ts's can be concatenated directly. Again probe deeply else can fail, esp on AD/subs. + # NOTE concat file better than concat stream: lipsync issues + # -copyts??? Without it ffmpeg reports duration correctly. Possibly not with copyts + cmd="$ffmpeg -i concat:"\"$MERGEFILES"\" -metadata title="\"$NAME"\" -c copy -flags +global_header $map "\"$OUTFILE"\" 2>> $LOGFILE" + echo;echo + logit "Executing $cmd" + sh -c $cmd + if [ $? -eq 0 ]; then + size=$(stat -c %s "$OUTFILE" ) + if [ $keep -eq 1 ]; then + # check for duration match within 20s (empirical magic number) + progduration $OUTFILE + if [ $(($duration + 20)) -lt $program_duration ]; then + logit "Possible fail: $OUTFILE, size $(($size/$meg))M, is only $duration sec long, original $(($totalsize/$meg))M, with length detected as $program_duration sec" + cleanup + quit 1 + fi + # check for silly compression ratio (converted less than 3/8 of original, or larger, heaven forbid!) + if [ $(($totalsize * 3)) -gt $(($size * 8)) -o $size -ge $totalsize -a $mpeg -eq 0 ]; then + logit "Possible fail: Suspect size of $OUTFILE, (new $(($size/$meg))M vs. original $(($totalsize/$meg))M)" + # keep converted outfile, actually it may be OK + cleanup + quit 1 + fi + # Save video, force a VDR reindex and check it was really successful before deleting original files + # VDR2 will only recognise .ts's in the NEW format of directory name + if [ $VDRtype ]; then + # Need a new recording DIR formatted for VDR2/ts files, else VDR2 won't see it + # TV channel starts at 1. Channel number unavailable in info.vdr (& can change), just set to 1 + # Resume ID 0 as resume different in VDR1.x + RECDIR=$(echo "$BASEDIR" | cut -f1-3 -d".") + RECDIR="$ROOTDIR/$RECDIR.1-0.rec" + mkdir -p -- "$RECDIR" + if [ $? -ne 0 ]; then + logit "Failed - cannot create $RECDIR" + cleanup + quit 1 + fi + # marks don't need to change - they are time-related not stream related + [ -f "$input/marks.vdr" ] && cp -f "$input/marks.vdr" "$RECDIR/marks" + # Add the priority and Lifetime tags to new info + cp -f "$input/info.vdr" "$RECDIR/info" + echo "P $priority" >> "$RECDIR/info" + echo "L $lifetime" >> "$RECDIR/info" + # no resume as format changed + else + # VDR2 - Save ts's and index + RECDIR="$input" + SAVE="$input/0*.ts" + for f in $SAVE + do + mv -f "$f" "$f.orig" || logit "could not save original file(s)" + done + mv -f "$input/index" "$input/index.orig" || logit "could not save index" + fi + # Only a single ts after transcoding + mv -f "$OUTFILE" "$RECDIR/00001.ts" || logit "could not move .ts" + # Neatness, but importantly a check that VDR will be reasonably happy to play the file - otherwise bail + sh -c "vdr --genindex "\"$RECDIR"\"" + if [ $? -eq 0 ]; then + stats="$(($totalsize/$meg))M to $(($size/$meg))M ($(echo $size $totalsize | awk '{ printf "%.1f", 100 * $1 / $2 }')%)" + echo "# Transcoded from $stats by $0 on $(date)" >> "$RECDIR/info" + [ "$F_VIDEO" ] && echo "# Video parameters: $reportedvideo" >> "$RECDIR/info" + [ "$F_AUDIO" ] && echo "# Audio parameters: $reportedaudio" >> "$RECDIR/info" + logit "Created/replaced .ts and indexed $NAME ($stats)" + if [ $delete -eq 1 ]; then + if [ $VDRtype ]; then + # old VDR 1.x directory & content no use now + [ $debug -eq 0 ] && rm -rf "$input" || logit "could not remove original file(s)" + else + [ $debug -eq 0 ] && rm -f "$input"/*.orig || logit "could not remove original file(s)" + fi + logit "Original files for $NAME deleted" + fi + else + # Failed, so restore + logit "Conversion / indexing for $NAME failed" + if [ $VDRtype ]; then + [ $debug -eq 0 ] && rm -rf "$RECDIR" || logit "could not delete temp conversion files" # borked new directory + else + # Overwrite + mv -f "$RECDIR/index.orig" "$RECDIR/index" || logit "could not restore index" + for f in $SAVE + do + mv -f "$f.orig" "$f" || logit "could not restore original video file" + done + fi + fi + # For audio-only recordings for use outside VDR, more portable to quickly remux to something more useful than .ts + elif [[ $DEF = "AUDIO" ]]; then + cmd="$ffmpeg -i "\"$OUTFILE"\" -c:a copy -bsf:a aac_adtstoasc "\"$AUDIO_ONLY"\" 2>> $LOGFILE" + sh -c $cmd + if [ $? -eq 0 ]; then + rm -f "$OUTFILE" + OUTFILE="$AUDIO_ONLY" + else + logit "Failed audio conversion to $AUDIO_ONLY" # leave it to check later + fi + fi + else + logit "Fail: Converted files for $NAME did not merge correctly" + fi + cleanup +fi + +[ $ftp -eq 1 -o $redo -eq 1 -a $debug -eq 0 ] && upload "$OUTFILE" + +# remove lockfile +quit 0 + +# --------- $Id: vdr-convert,v 1.3 2016/09/01 13:01:48 richard Exp $ ---------- END |