Fixed various bugs in video copy and merge scripts
authorAlan J. Pippin <alan@pippins.net>
Sat, 29 Oct 2022 17:30:12 +0000 (11:30 -0600)
committerAlan J. Pippin <ajp@pippins.net>
Sat, 29 Oct 2022 17:30:12 +0000 (11:30 -0600)
Added support to extract video taken date from file vs relying alone on modification time

NameMyTVSeries [new file with mode: 0755]
aired_today_playlist.py [new file with mode: 0755]
check_phone_videos_are_in_originals [new file with mode: 0755]
ffprobe [new file with mode: 0755]
make_mkv
merge_videos_by_day
mkv2dvd [new file with mode: 0755]
move_videos_from_watchdir
organize_videos
organize_videos.conf
remove_empty_tv_dirs [new file with mode: 0755]

diff --git a/NameMyTVSeries b/NameMyTVSeries
new file mode 100755 (executable)
index 0000000..fda7b56
Binary files /dev/null and b/NameMyTVSeries differ
diff --git a/aired_today_playlist.py b/aired_today_playlist.py
new file mode 100755 (executable)
index 0000000..c06e5e8
--- /dev/null
@@ -0,0 +1,77 @@
+"""
+Create a Plex Playlist with what aired on this day in history (month-day), sort by oldest first.
+If Playlist from yesterday exists delete and create today's.
+If today's Playlist exists exit.
+"""
+
+import operator
+from plexapi.server import PlexServer
+import requests
+import datetime
+
+PLEX_URL = 'http://localhost:32400'
+PLEX_TOKEN = 'xxxxx'
+
+LIBRARY_NAMES = ['Movies', 'TV Shows'] # Your library names
+
+today = datetime.datetime.now().date()
+
+TODAY_PLAY_TITLE = 'Aired Today {}-{}'.format(today.month, today.day)
+
+plex = PlexServer(PLEX_URL, PLEX_TOKEN)
+
+def remove_old():
+    # Remove old Aired Today Playlists
+    for playlist in plex.playlists():
+        if playlist.title.startswith('Aired Today') and playlist.title != TODAY_PLAY_TITLE:
+            playlist.delete()
+            print('Removing old Aired Today Playlists: {}'.format(playlist.title))
+        elif playlist.title == TODAY_PLAY_TITLE:
+            print('{} already exists. No need to make again.'.format(TODAY_PLAY_TITLE))
+            exit(0)
+
+
+def get_all_content(library_name):
+    # Get all movies or episodes from LIBRARY_NAME
+    child_lst = []
+    for library in library_name:
+        for child in plex.library.section(library).all():
+            if child.type == 'movie':
+                child_lst += [child]
+            elif child.type == 'show':
+                child_lst += child.episodes()
+            else:
+                pass
+    return child_lst
+
+
+def find_air_dates(content_lst):
+    # Find what aired with today's month-day
+    aired_lst = []
+    for video in content_lst:
+        try:
+            ad_month = str(video.originallyAvailableAt.month)
+            ad_day = str(video.originallyAvailableAt.day)
+            
+            if ad_month == str(today.month) and ad_day == str(today.day):
+                aired_lst += [[video] + [str(video.originallyAvailableAt)]]
+        except Exception as e:
+            # print(e)
+            pass
+        
+        # Sort by original air date, oldest first
+        aired_lst = sorted(aired_lst, key=operator.itemgetter(1))
+
+    # Remove date used for sorting
+    play_lst = [x[0] for x in aired_lst]
+    return play_lst
+
+
+remove_old()
+play_lst = find_air_dates(get_all_content(LIBRARY_NAMES))
+# Create Playlist
+if play_lst:
+    plex.createPlaylist(TODAY_PLAY_TITLE, play_lst)
+else:
+    print('Found nothing aired on this day in history.')
+
diff --git a/check_phone_videos_are_in_originals b/check_phone_videos_are_in_originals
new file mode 100755 (executable)
index 0000000..f002e59
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+# Checks to make sure all videos off of phone have been copied to the Originals directory
+# If they are missing, it means Nextcloud missed them somehow. Copy them to New Memories then.
+
+dcim_path="/storage/emulated/0/DCIM/Camera"
+originals="/naspool/dropbox/Originals"
+new_memories="/naspool/dropbox/New Memories"
+
+adb connect 192.168.0.23
+if [[ $? != 0 ]]; then
+  echo "#ERROR: Unable to connect to phone at 192.168.0.23"
+  exit 1
+fi
+
+mp4s_on_phone=$(adb -e shell ls -1 $dcim_path/*.mp4 | xargs -n 1 basename)
+mp4s_in_originals=$(/bin/ls -1 $originals/*.mp4 | xargs -n 1 basename)
+
+for i in $mp4s_on_phone; do
+  echo "$mp4s_in_originals" | grep -q $i
+  if [[ $? == 1 ]]; then
+    echo "mp4 not found in originals: $i"
+    adb -e pull "$dcim_path/$i" "$new_memories"
+  fi
+done
diff --git a/ffprobe b/ffprobe
new file mode 100755 (executable)
index 0000000..9d0198b
Binary files /dev/null and b/ffprobe differ
index bf983252ee2d5c6c442265f67807c872f5b0202d..f0481165d5a0dde18e56d347cb8952ad0d2e921b 100755 (executable)
--- a/make_mkv
+++ b/make_mkv
@@ -17,6 +17,10 @@ use Data::Dumper;
 use DateTime;
 use DateTime::Duration;
 use DateTime::Format::Duration;
+use DateTime::Format::Strptime qw( );
+
+# Set our ffmpeg creation_time format
+$ffmpeg_time_format = DateTime::Format::Strptime->new(pattern=>'%Y-%m-%dT%H:%M:%S', time_zone => 'UTC', on_error => 'croak');
 
 ####################################################################################################
 # Configuration parameters
@@ -66,7 +70,15 @@ sub epoch_to_date {
 my %videos;
 foreach $video (split(/,/, $opt_i)) {
     if(! -r "$video") { die "-E- Unable to read input video file: $video\n"; }
-    my $mtime_epoch = stat("$video")->mtime;
+    my $mtime_epoch = 0;
+    my $creation_time = `$ffmpeg -i "$video" 2>&1 | grep "creation_time" | head -n 1 | awk '{print \$3}'`;
+    if($creation_time) {
+       my $date_taken = $ffmpeg_time_format->parse_datetime($creation_time);
+       $date_taken->set_time_zone('local');
+       $mtime_epoch = $date_taken->epoch;
+    } else {
+       $mtime_epoch = stat("$video")->mtime;
+    }
     my $mdate = epoch_to_date($mtime_epoch);
     $videos{$video} = $mtime_epoch;
 }
@@ -191,6 +203,10 @@ foreach my $video (sort{$videos{$a} <=> $videos{$b}} keys %videos) {
                    unlink "$video_mp4";
                    die "-E- handbrake encountered some errors with exit code $errno\n";
                }
+               # Restore the metadata from the original video to the intermediate video to preserve the creation_time of the original video metadata in it
+               `$ffmpeg -i \"$video\" -i \"$video_mp4\" -map 1 -map_metadata 0 -c copy -movflags use_metadata_tags \"$video_mp4.fixed.mp4\" >> \"$tmpfile\" 2>&1`;
+               $errno=system("mv \"$video_mp4.fixed.mp4\" \"$video_mp4\" 2>/dev/null");
+               if($errno) { print "-E- Error moving fixed metadata video back to dstfile: $video_mp4.fixed.mp4 -> $video_mp4\n"; }
            }
        } else {
            if($no_recompress) { 
@@ -273,7 +289,15 @@ foreach my $key (keys %merge_videos) {
        my $hour = 0;
        my $min  = 5;
        my $sec  = 0;
-       my $mtime_epoch = stat("$video")->mtime;
+       my $mtime_epoch = 0;
+       my $creation_time = `$ffmpeg -i "$video" 2>&1 | grep "creation_time" | head -n 1 | awk '{print \$3}'`;
+       if($creation_time) {
+           my $date_taken = $ffmpeg_time_format->parse_datetime($creation_time);
+           $date_taken->set_time_zone('local');
+           $mtime_epoch = $date_taken->epoch;
+       } else {
+           $mtime_epoch = stat("$video")->mtime;
+       }
        my $mdate = epoch_to_date($mtime_epoch);
        my $duration = `$ffmpeg -i "$video" 2>&1 | grep Duration`;
        if($duration =~ /Duration: (\d+):(\d+):(\d+)\.(\d+)/) {
@@ -309,7 +333,7 @@ foreach my $key (keys %merge_videos) {
     }
     print "\n$merge_cmd\n";
     if(! defined $opt_s) {
-       if(-f "$opt_o.$key.mkv") { die "-E- Output video file $opt_o already exists. This is unexpected for key $key!"; }
+       #if(-f "$opt_o.$key.mkv") { die "-E- Output video file $opt_o already exists. This is unexpected for key $key!"; }
        my $errno = system("$merge_cmd");
        $errno = $errno >> 8;
        if($errno > 1) {
index 146ac84bad6b8c38187ad78fc1a0ca96cdabc24f..193a7bdf32da50c262e3e686a0bab9df9f3621dd 100755 (executable)
@@ -7,6 +7,11 @@ use File::Basename;
 use Getopt::Std;
 use File::stat;
 use Time::localtime;
+use DateTime::Format::Strptime qw( );
+
+# Set our ffmpeg creation_time format
+$ffmpeg_time_format = DateTime::Format::Strptime->new(pattern=>'%Y-%m-%dT%H:%M:%S', time_zone => 'UTC', on_error => 'croak');
+$ctime_format = DateTime::Format::Strptime->new(pattern=>'%a %b %d %H:%M:%S %Y', time_zone => 'local', on_error => 'croak');
 
 # Early command line options processing
 getopts("qzkh:tvs:");
@@ -96,12 +101,28 @@ foreach $file (sort `$find_cmd`) {
     if($srcfile =~ /.hb.mp4/) { next; }
 
     print "Found video: srcdir: $srcdir srcfile: $srcfile srcext: $srcext dstext: $ext\n" if($opt_v);
-            
-    # Make a note of the month, year, and day this video was taken (from the modification time of the file)
-    $date_taken = ctime(stat("$srcdir/$srcfile")->mtime);
-    
+
+    # Make a note of the month, year, and day this video was taken
+    $creation_time = `$ffmpeg -i "$srcdir/$srcfile" 2>&1 | grep "creation_time" | head -n 1 | awk '{print \$3}'`;
+    if($creation_time) {
+       $date_taken = $ffmpeg_time_format->parse_datetime($creation_time);
+    } else {
+       # From the modification time of the file since we couldn't find it in the video file
+       $date_modified = ctime(stat("$srcdir/$srcfile")->mtime);
+    }
+
+    # Get the date taken from the ffmpeg creation_time
+    if(!$merge_by_modification_time && $date_taken) {
+       $date_taken->set_time_zone('local');
+       $year = $date_taken->year;
+       $month = sprintf("%02d",$date_taken->month);
+       $day = sprintf("%02d",$date_taken->day);
+       $monthnum = sprintf("%02d",$date_taken->month);
+       $monthname = lc($month2monthname{$month});
+       #print "date_taken: $year-$month-$day ".$date_taken->hour.":".$date_taken->minute.":".$date_taken->second." ".$date_taken->time_zone."\n";
+    }
     # Get the date taken from the filename
-    if(!$merge_by_modification_date && $srcfile =~ /^(\d\d\d\d)-(\d\d)-(\d\d)/) {
+    elsif(!$merge_by_modification_date && $srcfile =~ /^(\d\d\d\d)-(\d\d)-(\d\d)/) {
        $year = $1;
        $month = $2;
        $day = sprintf("%02d",$3);
@@ -124,7 +145,7 @@ foreach $file (sort `$find_cmd`) {
        $monthname = lc($month2monthname{$month});
     }
     # Get the date taken from the modification time
-    elsif($date_taken =~ /\S+\s+(\S+)\s+(\d+)\s+\S+\s+(\d+)/) {
+    elsif($date_modified =~ /\S+\s+(\S+)\s+(\d+)\s+\S+\s+(\d+)/) {
        $year = $3;
        $month = $1;
        $day = sprintf("%02d",$2);
diff --git a/mkv2dvd b/mkv2dvd
new file mode 100755 (executable)
index 0000000..cc07c43
--- /dev/null
+++ b/mkv2dvd
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+SRC=$1
+NAME="${SRC%.*}"
+MPG="${NAME}.mpg"
+DVD="${NAME}.dvd"
+ISO="${NAME}.iso"
+
+export VIDEO_FORMAT=NTSC
+
+if [[ -z $SRC ]] || [[ ! -r $SRC ]]; then
+    echo "-E- You need to specify a valid src video file as an argument"
+    exit 1
+fi
+
+if [[ ! -e "$MPG" ]]; then
+    echo "-> Converting \"$SRC\" -> \"$MPG\""
+    # Convert the SRC video into a DVD compatible mpg2 file
+    ffmpeg -i "$SRC" -target ntsc-dvd -aspect 16:9 "$MPG"
+    if [[ $? != 0 ]]; then
+       echo "-> ffmpeg encountered an error. aborting..."
+       rm "$MPG"
+       exit 1;
+    fi
+fi
+
+echo "-> Creating DVD ISO: $ISO"
+# Create an intermediate DVD dir
+mkdir -p "$DVD"
+# Define the title file
+dvdauthor -o "$DVD" -t "$MPG"
+# Create a table of contents
+dvdauthor -o "$DVD" -T
+# Convert the DVD dir into an ISO image
+genisoimage -dvd-video -o "$ISO" "$DVD"
+# Remove the intermediate DVD dir
+rm -rf "$DVD"
+# Remove the intermediate MPG file
+rm "$MPG"
index 364ade6575180ba5644e87ada1a9ba7db8f0238d..864d33e1c8265869001dd8202fc1ce33f37e0c2e 100755 (executable)
@@ -85,7 +85,7 @@ foreach $watchpath (split(';', $watchpathname)) {
         } 
 
        # Make sure we have a unique dstfile
-       if(-f "$dstfile") {
+       if($overwrite_dest == 0 && -f "$dstfile") {
            $video_ext = $srcfile;
            $video_ext =~ s/.*\.(\S+)$/$1/;
            $suffix = 0;
@@ -102,18 +102,22 @@ foreach $watchpath (split(';', $watchpathname)) {
 
        if(!defined $opt_t) {
            # Make sure the dstfile doesn't exist, if it does, add a unique number to the end
-           if(! -f "$dstfile") {
-               $errno=system("mv \"$srcdir/$srcfile\" \"$dstfile\" 2>/dev/null");
-               if($errno) { print "-E- Error moving srcfile to dstfile: $srcdir/$srcfile -> $dstfile\n"; next; }
-           } else {
-               print "-E- Unable to mv $srcdir/$srcfile -> $dstfile because it already exists\n";
-           }
+           #if(! -f "$dstfile") {
+           $errno=system("mv \"$srcdir/$srcfile\" \"$dstfile\" 2>/dev/null");
+           if($errno) { print "-E- Error moving srcfile to dstfile: $srcdir/$srcfile -> $dstfile\n"; next; }
+           #} else {
+           #   print "-E- Unable to mv $srcdir/$srcfile -> $dstfile because it already exists\n";
+           #}
            # Fix the permissions
            system("chown $owner \"$dstfile\"");
            system("chgrp $group \"$dstfile\"");
            system("chmod $mode \"$dstfile\"");
        }
     }
+
+    # Update nextcloud file cache so it knows what files we have moved
+    $watchpath =~ s/$nextcloudbase//g;
+    system("$occ files:scan --path \"$watchpath\" > /dev/null");
 }
                    
 $pidfile->remove();
index 05edab463cb246cba2357155bd67c6985c5ed1bc..d62af19c673c5f2f518001164ba3d11319d00eb2 100755 (executable)
@@ -179,10 +179,13 @@ foreach $file (`$find_mkv`) {
     print "srcfile: $srcfile\n";
     if($srcfile =~ /.hb.mp4/) { next; }
     
-    print "Found movie: srcdir: $srcdir srcfile: $srcfile ext: $ext\n" if($opt_v);
-        
-    # Make a note of the month, year, and day this video was taken (from the modification time of the file)
-    $date_taken = ctime(stat("$srcdir/$srcfile")->mtime);
+    print "Found video: srcdir: $srcdir srcfile: $srcfile ext: $ext\n" if($opt_v);
+
+    # From the modification time of the file since we couldn't find it in the filename
+    $date_modified = ctime(stat("$srcdir/$srcfile")->mtime);
+
+    # NOTE: This file matching algorithm only applies to videos produced by merge_videos_by_day called by this script earlier
+    # We just need to take those merged videos, and extract what date to call the video by under HomeVideos
 
     # Get the date taken from the filename
     if($srcfile =~ /^(\d+)-(\d+)-(\d+)/) {
@@ -208,7 +211,7 @@ foreach $file (`$find_mkv`) {
        $monthname = lc($month2monthname{$month});
     }
     # Get the date taken from the modification time
-    elsif($date_taken =~ /\S+\s+(\S+)\s+(\d+)\s+\S+\s+(\d+)/) {
+    elsif($date_modified =~ /\S+\s+(\S+)\s+(\d+)\s+\S+\s+(\d+)/) {
        $year = $3;
        $month = $1;
        $day = sprintf("%02d",$2);
index fd1241345f1b2e5b670d013b7719999bcd6ec50c..27b793369ee0540d1c4e52def9651e41a0bb0361 100644 (file)
@@ -26,11 +26,20 @@ $save_originals = 1;
 # Path to a dir (or dirs separated by semis) to watch for videos to move to $srcpathname to be organized
 $watchpathname = "/naspool/cloud/alan/files/InstantUpload/Camera;/naspool/cloud/mary/files/InstantUpload/Camera;";
 
+# Path to nextcloud base dir where user dirs start
+$nextcloudbase = "/naspool/cloud/";
+
+# If there are file duplicates in srcpathname, should they be overwritten (assumes all files from phones will be named uniquely)
+$overwrite_dest = 1;
+
+# Path to nextcloud occ cmd script
+$occ = "/naspool/www/pippins.net/nextcloud.custom/occ.cmd";
+
 # Path to merge_videos_by_day script
 $merge_videos_by_day = "/naspool/videos/bin/merge_videos_by_day";
 
 # Flag to merge videos by modification date instead of the date parsed from the filename
-$merge_by_modification_date = 1;
+$merge_by_modification_date = 0;
 
 # Path to the make_mkv script
 $make_mkv = "/naspool/videos/bin/make_mkv";
@@ -76,7 +85,7 @@ $no_recompress_file_ext = qr/\.(mp4)$/i;
 
 # Video file creation dates must not have changed in the last X minutes to process any of the video files
 # This is done to ensure that all videos from a given upload from a camera have completed prior to looking for videos to merge
-$minage = "+120";
+$minage = "+60";
 
 # What command should be used to find files that have changed (are at least $minage old) 
 $find_changed_cmd = "find  \"$srcpathname/\" -not -cmin $minage -a \\( $movie_file_ext \\)";
diff --git a/remove_empty_tv_dirs b/remove_empty_tv_dirs
new file mode 100755 (executable)
index 0000000..b9b7941
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/perl
+# Author: Alan J. Pippin
+# Description: Find and remove TV recording directories that don't contain any recordings in them
+
+use File::Copy;
+use File::Basename;
+use Getopt::Std;
+use File::stat;
+use Time::localtime;
+use File::Pid;
+use Data::Dumper;
+
+####################################################################################################
+# Configuration parameters
+my $tv_src_dir = "/tvpool/tv";
+my $log = "/var/log/organize/remove_empty_tv_dirs.log";
+my $tv_file_ext = "-iregex \".*\.mov\" -o -iregex \".*\.avi\" -o -iregex \".*\.mp4\" -o -iregex \".*\.mpg\" -o -iregex \".*\.ts\" -o -iregex \".*\.mkv\"";
+my $find_tv_show_dirs = "find \"$tv_src_dir/\" -maxdepth 1 -cmin +14400 -type d";
+my $find_tv_season_dirs = "find \"$tv_src_dir/\" -maxdepth 2 -mindepth 2 -cmin +14400 -type d";
+my $find_tv_shows = "find \"$tv_src_dir/\" -cmin +14400 $tv_file_ext";
+
+####################################################################################################
+# Find empty TV show directories to remove
+
+my %tv_show_dirs;
+
+# Find all of our TV season dirs and store them in a hash
+foreach $dir (`$find_tv_season_dirs`) {
+    chomp($dir);
+    my $season = basename($dir);
+    my $show = basename(dirname($dir));
+    #print "-> Found dir $dir show $show season $season\n";
+    $tv_show_dirs{$show}{$season}{has_recordings} = 0;
+}
+foreach $dir (`$find_tv_show_dirs`) {
+    chomp($dir);
+    my $show = basename($dir);
+    #print "-> Found dir $dir show $show\n";
+    $tv_show_dirs{$show}{has_recordings} = 0;
+}
+
+# Find all of our TV show recordings and store them in a hash
+foreach $file (`$find_tv_shows`) {
+    chomp($file);
+    my $recording = basename($file);
+    my $season = basename(dirname($file));
+    my $show = basename(dirname(dirname($file)));
+    #print "-> Found show $show season $season and recording $recording\n";
+    $tv_show_dirs{$show}{$season}{has_recordings} = 1;
+    $tv_show_dirs{$show}{has_recordings} = 1;
+}
+
+#print Dumper(\%tv_show_dirs);
+
+# For each season directory that doesn't have recordings, remove it
+foreach $show (keys %tv_show_dirs) {
+    foreach $season (keys %{$tv_show_dirs{$show}}) {
+       if($season eq "has_recordings") { next; }
+       if ($tv_show_dirs{$show}{$season}{has_recordings} == 0) {
+           print "-> Removing season dir '$show/$season'\n";
+           system("rm -rf \"$tv_src_dir/$show/$season/\"");
+       }
+    }
+}
+
+# For each show directory that doesn't have any recordings under it, remove it
+foreach $show (keys %tv_show_dirs) {
+    my $has_recordings = 0;
+    if($show eq ".grab") { next; }
+    foreach $season (keys %{$tv_show_dirs{$show}}) {
+       if ($tv_show_dirs{$show}{$season}{has_recordings} == 1) {
+           $has_recordings = 1;
+       }
+    }
+    if($has_recordings == 0) { 
+       print "-> Removing show dir '$show'\n";
+       system("rm -rf \"$tv_src_dir/$show/\"");
+    }
+}