#!/mod/bin/jimsh source /mod/webif/lib/setup require lock system.class ts.class tdelete pretty_size set debug 0 if {![acquire_lock webif_auto]} { puts "Cannot acquire exclusive lock, terminating." exit } set logfile "/mod/tmp/auto.log" # Rotate log file if large enough. if {[file exists $logfile] && [file size $logfile] > 2097152} { file copy -force $logfile "/mod/tmp/auto_old.log" file delete $logfile } set logfd [open "/mod/tmp/auto.log" "a+"] proc log {msg {always 0}} { if {!$::debug && !$always} return puts $::logfd "[\ clock format [clock seconds] -format "%d/%m/%Y %H:%M"\ ] - $msg" flush $::logfd } proc elapsed {start} { return $(([clock milliseconds] - $start) / 1000.0) } proc startclock {} { set ::startclock_s [clock milliseconds] } proc endclock {size} { set el [elapsed $::startclock_s] set rate $($size / $el) return "[pretty_size $size] in $el seconds - [pretty_size $rate]/s" } set scanstart [clock milliseconds] log "-------------------------------------------------------" 1 # is_listening is relatively expensive so it is checked once globally at # the start and then if the server is not listening then no decrypt # operations will be attempted for this run, even if the server starts # up halfway through. Otherwise the server is checked for every decryption # and if it goes away then decryption will not be attempted for the rest # of the run. if {[system is_listening 9000]} { set dlnaok 1 if {$::debug} { log "DLNA Server is running." } } else { set dlnaok 0 if {$::debug} { log "DLNA Server is NOT running." } } log "Media scan starting, DLNA server status: $dlnaok" 1 proc dsc {{size 0}} { set free [system diskfree] # Required disk space is 1GiB + 3 times the file size. set req $($size * 3 + 1073741824) if {$free < $req} { log "Insufficient disk space. Require=$req, Free=$free" 1 exit } } dsc set tmp "/mod/tmp/webif_auto" if {![file exists $tmp]} { if {[catch {file mkdir $tmp} msg]} { log "Cannot create temporary directory - $tmp ($msg)" 1 exit } } elseif {![file isdirectory $tmp]} { log "Cannot create temporary directory - $tmp (file exists)" 1 exit } # Clean-up the temporary directory foreach file [readdir -nocomplain $tmp] { tdelete "$tmp/$file" } if {[system pkginst undelete]} { set dustbin "[system dustbin]" } else { set dustbin "" } log "Dustbin: $dustbin" proc bindir {file binroot} { set dir [file dirname $file] regsub "^[system mediaroot]" $dir $binroot ndir if {$dir eq $ndir} { set ndir $binroot } system mkdir_p $ndir return $ndir } proc dedup {dir} { log "DEDUP: \[$dir]" loop i 0 2 { foreach line [split \ [exec /mod/webif/html/dedup/dedup -yes -auto $dir] "\n"] { log $line 1 } } } proc do_shrink {ts} { global tmp dustbin tsgroup set file [file rootname [$ts get file]] if {[catch { set perc [exec /mod/bin/stripts -aq $file] } msg]} { log " Error: $msg" 1 return } if {[string match {*%} $perc]} { set perc [string range $perc 0 end-1] } else { set perc 0 } if {$perc == 0} { log " Already shrunk." return } set size [$ts size] dsc $size startclock log " SHRINK: $file" 1 log " Estimate $perc% saving." 1 log " Shrinking..." 1 if {[catch { foreach line [split \ [exec nice -n 19 /mod/bin/stripts -q $file $tmp/shrunk] \ "\n"] { log $line 1 } } msg]} { log "Error during shrink: $msg" 1 return } # The following steps are structured to minimise the risk of # things being left in an inconsistent state if the system goes # into standby. Renames within the same filesystem are very # quick so the risk is small, but even so... # Move the shrunken version back to the local directory. foreach f [glob "$tmp/shrunk.*"] { set ext [file extension $f] file rename $f "${file}_shrunk${ext}" } # Move the old recording to the bin if undelete is installed. if {$dustbin ne ""} { $ts move [bindir $file "$dustbin/webif_autoshrink"] 1 1 } else { # Delete otherwise. if {[$ts delete]} { log "Successfully deleted $file." } else { log "Problem deleting $file, [$ts get error]" 1 return } } # Finally, rename the shrunken recording again. foreach ext $tsgroup { set f "${file}_shrunk.$ext" if {[file exists $f]} { file rename $f "${file}.$ext" } } log "Done... [endclock $size]" 1 } proc do_decrypt {ts} { global tmp dustbin set file [$ts get file] set rfile [file rootname $file] set bfile [file tail $file] if {![$ts flag "ODEncrypted"]} { log " Already decrypted." return } lassign [$ts dlnaloc] url if {$url eq ""} { log " Not yet indexed." return } if {![system is_listening 9000]} { log " DLNA Server not running." set ::dlnaok 0 return } set size [$ts size] dsc $size startclock log " DECRYPT: $rfile" 1 log " DLNA: $url" 1 exec wget -O "$tmp/$bfile" $url if {[file size $file] != [file size "$tmp/$bfile"]} { log " File size mismatch." 1 return } # Move the encrypted file out of the way. file rename $file "$rfile.encrypted" # Move the decrypted copy into place. file rename "$tmp/$bfile" $file # Patch the HMT - quickest way to get back to a playable file. exec /mod/bin/hmt -encrypted "$rfile.hmt" log " Removing/binning old copy." # Move the old recording to the bin if undelete is installed. if {$dustbin ne ""} { set bin [bindir $file "$dustbin/webif_autodecrypt"] set tail [file tail $rfile] file rename "$rfile.encrypted" "$bin/$tail.ts" foreach ext {nts hmt thm} { if {[file exists "$rfile.$ext"]} { file copy $rfile.$ext "$bin/$tail.$ext" if {$ext eq "hmt"} { # Patch the binned HMT back exec /mod/bin/hmt +encrypted \ "$bin/$tail.hmt" } } } } else { tdelete "$rfile.encrypted" } log "Done... [endclock $size]" 1 } proc do_mpg {ts} { global tmp tsgroup set file [file rootname [$ts get file]] if {[file exists $file.mpg]} { # Already done. return } if {[$ts flag "ODEncrypted"]} { log " Not decrypted." return } if {[$ts get definition] eq "HD"} { # Cannot extract a useful MP3 from a HD recording. return } dsc [$ts size] log " MPG: $file" 1 log " Converting..." 1 if {[catch { foreach line [split \ [exec nice -n 19 /mod/bin/ffmpeg -y -benchmark -v 0 \ -i $file.ts \ -map 0:0 -map 0:1 \ -vcodec copy -acodec copy $tmp/mpg.mpg] "\n"] { log $line 1 } } msg]} { log "Error during mpg extract: $msg" 1 return } # Move the MPG into the local directory file rename $tmp/mpg.mpg $file.mpg } proc entries {dir callback} { foreach entry [readdir -nocomplain $dir] { if {![string match {*.ts} $entry} continue if {[catch {set ts [ts fetch "$dir/$entry"]}]} continue if {$ts == 0} continue if {[system inuse [file rootname "$dir/$entry"]]} { log "$entry - in use\n" 1 continue } #dsc [file size "$dir/$entry"] $callback $ts } } proc shrink {dir} { log "SHRINK: \[$dir]" entries $dir do_shrink } proc decrypt {dir} { log "DECRYPT: \[$dir]" if {$::dlnaok} { entries $dir do_decrypt } } proc mpg {dir} { log "MPG: \[$dir]" entries $dir do_mpg } proc scan {dir attr {force 0}} {{indent 0}} { global dustbin incr indent 2 log "[string repeat " " $indent]\[$dir]" if {$dir eq $dustbin} { log "Dustbin, skipping." return } if {[string match {\[*} [file tail $dir]]} { # Special folder file stat "$dir/" st if {$st(dev) != $::rootdev} { log "Special folder on different device, skipping." return } if {$force} { set force 0 log "Special folder, overriding recursion." } } # Recursion if {!$force && [file exists "$dir/.auto${attr}r"]} { log "[string repeat " " $indent] (R)" set force 1 } dsc if {$force || [file exists "$dir/.auto$attr"]} { $attr $dir } foreach entry [readdir -nocomplain $dir] { if {[file isdirectory "$dir/$entry"]} { scan "$dir/$entry" $attr $force } } incr indent -2 } set root [system mediaroot] file stat "$root/" rootstat set rootdev $rootstat(dev) #log "Root device: $rootdev" 1 if {[llength $argv] > 0} { if {[lindex $argv 0] eq "test"} { set debug 1 } foreach arg $argv { scan $root $arg } } else { foreach arg {dedup decrypt shrink mpg} { set st [clock milliseconds] scan $root $arg log "$arg scan completed in [elapsed $st] seconds." 1 } } release_lock webif_auto log "Media scan completed in [elapsed $scanstart] seconds." 1