-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProgram.cs
215 lines (183 loc) · 8.52 KB
/
Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using CommandLine;
namespace photo_organiser
{
class Program
{
public class Options
{
[Option('i', "inputdir", Required = true, HelpText = "Input directory containing photos and movies.")]
public string InputDir { get; set; }
[Option('o', "outputdir", Required = true, HelpText = "Output directory to copy sorted and shrunk photos and movies.")]
public string OutputDir { get; set; }
}
public class FileAndDirectoryService
{
private Regex r;
private Dictionary<string, List<string>> fileList;
public FileAndDirectoryService()
{
r = new Regex(":");
fileList = new Dictionary<string, List<string>>();
}
public void ProcessDir(string inputDir, string outputDir)
{
var files = Directory.GetFiles(inputDir, "*", SearchOption.AllDirectories);
foreach(var f in files)
{
try
{
var extension = Path.GetExtension(f).ToLower();
if (extension == ".jpg") {
AddImage(f);
} else if (extension == ".mp4" || extension == ".mpg" || extension == ".3gp" || extension == ".avi" || extension == ".mov") {
AddMovie(f);
} else {
Console.WriteLine($"Unknown extension {extension} for file {f}.");
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception Occured: {ex.Message} for file {f}.");
}
}
}
public void SaveOutput(string outputDir)
{
foreach(var f in fileList)
{
DateTime date;
if (!DateTime.TryParse(f.Key, out date))
{
date = DateTime.UnixEpoch;
}
var year = date.Year.ToString();
var destDir = Path.Join(outputDir, Path.Join(year, f.Key));
// Make sure directory exists
Directory.CreateDirectory(destDir);
foreach (var fileName in f.Value)
{
var destFileName = Path.Join(destDir, Path.GetFileName(fileName));
File.Copy(fileName, destFileName);
// Set date since we might use this to determine when the video was taken
File.SetLastWriteTime(destFileName, date);
File.SetCreationTime(destFileName, date);
}
}
}
private void AddImage(string f)
{
var date = GetDateTakenFromImage(f);
var dateKey = date.ToString("yyyy-MM-dd");
Console.WriteLine($"File {f} was taken on {dateKey}");
if (!fileList.ContainsKey(dateKey)) {
fileList[dateKey] = new List<string>();
}
fileList[dateKey].Add(f);
}
private void AddMovie(string fullPath)
{
var fileName = Path.GetFileName(fullPath);
var regex = new Regex(@"(\d{4})(\d{2})(\d{2})");
var dateKey = "";
var match = regex.Match(fileName);
// If the string contains a 20 and it's at least 8 characters long
if (match.Success)
{
dateKey = $"{match.Groups[1]}-{match.Groups[2]}-{match.Groups[3]}";
} else {
dateKey = File.GetLastWriteTime(fullPath).ToString("yyyy-MM-dd");
}
Console.WriteLine($"File {fullPath} was taken on {dateKey}");
// Attempt to shrink movie using ffmpeg, and use new filename if it is smaller
// Create a temp file with same name in $TMP folder
var destFile = Path.GetTempPath() + fileName;
// Delete file if already exists
File.Delete(destFile);
// Run ffmpeg to try and shrink the file
// libx264 = CPU
// H264_AMF = GPU
// 264
//var cmdLineArgs = $"-i \"{fullPath}\" -c:v h264_amf -preset medium -crf 25 -movflags +faststart -acodec aac -strict experimental -ab 96k \"{destFile}\"";
// 265
var cmdLineArgs = $"-i \"{fullPath}\" -c:v libx265 -preset medium -crf 25 -movflags +faststart -acodec aac -strict experimental -ab 96k \"{destFile}\"";
// Used RX 6600 XT for hardware encoding h264
//var cmdLineArgs = $"-i \"{fullPath}\" -c:v hevc_amf -rc cqp -qp_i 10 -qp_p 15 -c:a copy -movflags +faststart \"{destFile}\"";
// Testing hw h265
//r cmdLineArgs = $"-i \"{fullPath}\" -vaapi_device /dev/dri/renderD128 -i 'concat:00001.ts|00002.ts|00003.ts' -vf 'format=nv12,hwupload' -map 0:0 -c:v hevc_vaapi -map 0:a -c:a copy -rc_mode CQP -global_quality 25 -profile:v main -v verbose \"{destFile}\"";
Console.WriteLine($"Executing ffmpeg {cmdLineArgs}");
// Set process info
var procInfo = new ProcessStartInfo("ffmpeg", cmdLineArgs);
procInfo.RedirectStandardOutput = true;
procInfo.UseShellExecute = false;
// wrap in exception trying to execute external process
try
{
var procResult = Process.Start(procInfo);
procResult.WaitForExit();
}
catch (Exception ex)
{
Console.WriteLine($"Exception Occured: {ex.Message} for file {fullPath}.");
}
// See if new file is indeed smaller
var fileSize = new FileInfo(fullPath).Length;
var newFileSize = new FileInfo(destFile).Length;
// Check if the newly produced file is smallere than the
var ratio = newFileSize / (float)fileSize;
if (ratio < 0.93)
{
var newRatio = (1 - ratio) * 100;
Console.WriteLine($"Using shrunk movie file {newRatio}% reduction).");
// Use new file as
fullPath = destFile;
}
// Get date string from filename
if (!fileList.ContainsKey(dateKey)) {
fileList[dateKey] = new List<string>();
}
fileList[dateKey].Add(fullPath);
}
//we init this once so that if the function is repeatedly called
//it isn't stressing the garbage man
//retrieves the datetime WITHOUT loading the whole image
public DateTime GetDateTakenFromImage(string path)
{
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
using (Image myImage = Image.FromStream(fs, false, false))
{
PropertyItem propItem = myImage.GetPropertyItem(36867);
string dateTaken = r.Replace(Encoding.UTF8.GetString(propItem.Value), "-", 2);
DateTime dateTime;
if (DateTime.TryParse(dateTaken, out dateTime))
{
return dateTime;
}
Console.WriteLine($"Unable to parse date {dateTaken} for file {path}.");
return DateTime.UnixEpoch;
}
}
}
static void Main(string[] args)
{
var fadService = new FileAndDirectoryService();
Parser.Default.ParseArguments<Options>(args).WithParsed<Options>(o =>
{
if (!Directory.Exists(o.InputDir) || !Directory.Exists(o.OutputDir))
{
Console.WriteLine($"Make sure directories exist.");
return;
}
fadService.ProcessDir(o.InputDir, o.OutputDir);
fadService.SaveOutput(o.OutputDir);
});
}
}
}