AM_CONFIG_HEADER(config.h)
AM_MAINTAINER_MODE
+AC_USE_SYSTEM_EXTENSIONS
AC_PROG_CC(gcc clang)
AC_ISC_POSIX
AC_PROG_CXX(g++ clang++)
#include <netinet/in.h>
#include <netdb.h>
+#include <regex.h>
+
#if !defined(_NO_UPDATE_CHECK)
#if defined(_OLD_WEBKIT)
#include <webkit.h>
return FALSE;
}
+static int
+match_by_pattern(const char *string, const char *pattern)
+{
+ int status;
+ regex_t re;
+ if (regcomp(&re, pattern, REG_EXTENDED|REG_NOSUB) != 0)
+ {
+ return 0;
+ }
+ status = regexec(&re, string, (size_t) 0, NULL, 0);
+ regfree(&re);
+ if (status != 0)
+ {
+ return 0;
+ }
+ return 1;
+}
+
+typedef struct {
+ const char *pattern;
+ const char *format;
+} datemap;
+
+static int
+parse_datestring(const char *src, struct tm *tm)
+{
+ datemap ymdThmsZ = {"[0-9]{4}-[0-1]?[0-9]-[0-3]?[0-9]T[0-9]{2}:[0-9]{2}:[0-9]{2}Z", "%Y-%m-%dT%H:%M:%SZ"};
+
+ datemap maps[1] = { ymdThmsZ };
+
+ for (int i = 0; i < sizeof(maps); i++)
+ {
+ if (match_by_pattern(src, maps[i].pattern))
+ {
+ strptime(src, maps[i].format, tm);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static char*
+get_creation_date(const char *pattern, const char *metaValue, const char *file)
+{
+ char date[11] = "";
+ if (metaValue != NULL && strlen(metaValue) > 1)
+ {
+ struct tm tm;
+ if (parse_datestring(metaValue, &tm))
+ {
+ strftime(date, 11, pattern, &tm);
+ }
+ }
+ else
+ {
+ struct stat stbuf;
+ if (g_stat(file, &stbuf) == 0){
+ struct tm *tm;
+ tm = localtime(&(stbuf.st_mtime));
+ strftime(date, 11, pattern, tm);
+ }
+ }
+ return strdup(date);
+}
+
static void
set_destination_settings(signal_user_data_t *ud, GhbValue *settings)
{
g_string_append_printf(str, "%s", dt);
p += strlen("{date}");
}
+ else if (!strncmp(p, "{creation-date}", strlen("{creation-date}")))
+ {
+ gchar *val;
+ const gchar *source = ghb_dict_get_string(ud->globals, "scan_source");
+ val = get_creation_date("%Y-%m-%d", ghb_dict_get_string(settings, "MetaReleaseDate"), source);
+ g_string_append_printf(str, "%s", val);
+ p += strlen("{creation-date}");
+ g_free(val);
+ }
+ else if (!strncmp(p, "{creation-time}", strlen("{creation-time}")))
+ {
+ gchar *val;
+ const gchar *source = ghb_dict_get_string(ud->globals, "scan_source");
+ val = get_creation_date("%H:%M", ghb_dict_get_string(settings, "MetaReleaseDate"), source);
+ g_string_append_printf(str, "%s", val);
+ p += strlen("{creation-time}");
+ g_free(val);
+ }
else if (!strncmp(p, "{quality}", strlen("{quality}")))
{
if (ghb_dict_get_bool(settings, "vquality_type_constant"))
</child>
<child>
<object class="GtkEntry" id="auto_name_template">
- <property name="tooltip_text" translatable="yes">Available Options: {source} {title} {preset} {chapters} {date} {time} {quality} {bitrate}</property>
+ <property name="tooltip_text" translatable="yes">Available Options: {source} {title} {preset} {chapters} {date} {time} {creation-date} {creation-time} {quality} {bitrate}</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
[self.formatTokenField setTokenizingCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@"%%"]];
[self.formatTokenField setCompletionDelay:0.2];
- _buildInFormatTokens = @[@"{Source}", @"{Title}", @"{Date}", @"{Time}", @"{Chapters}", @"{Quality/Bitrate}"];
+ _buildInFormatTokens = @[@"{Source}", @"{Title}", @"{Date}", @"{Time}", @"{Creation-Date}", @"{Creation-Time}", @"{Chapters}", @"{Quality/Bitrate}"];
[self.builtInTokenField setTokenizingCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@"%%"]];
[self.builtInTokenField setStringValue:[self.buildInFormatTokens componentsJoinedByString:@"%%"]];
@class HBChapter;
@class HBPreset;
+@interface HBMetadata : NSObject
+
+@property (nonatomic, readonly, nullable) NSString *name;
+@property (nonatomic, readonly, nullable) NSString *artist;
+@property (nonatomic, readonly, nullable) NSString *composer;
+@property (nonatomic, readonly, nullable) NSString *releaseDate;
+@property (nonatomic, readonly, nullable) NSString *comment;
+@property (nonatomic, readonly, nullable) NSString *album;
+@property (nonatomic, readonly, nullable) NSString *albumArtist;
+@property (nonatomic, readonly, nullable) NSString *genre;
+@property (nonatomic, readonly, nullable) NSString *description;
+@property (nonatomic, readonly, nullable) NSString *longDescription;
+
+@end
+
/**
* HBTitles is an interface to the low-level hb_title_t.
* the properties are lazy-loaded.
@property (nonatomic, readonly) NSArray<NSDictionary<NSString *, id> *> *subtitlesTracks;
@property (nonatomic, readonly) NSArray<HBChapter *> *chapters;
+@property (nonatomic, readonly) HBMetadata *metadata;
+
@end
NS_ASSUME_NONNULL_END
extern NSString *keySubTrackLanguageIsoCode;
extern NSString *keySubTrackType;
+@interface HBMetadata ()
+
+@property (nonatomic, readonly) hb_metadata_t *hb_metadata;
+@property (nonatomic, copy) NSString *releaseDate;
+
+@end
+
+@implementation HBMetadata
+- (instancetype)initWithMetadata:(hb_metadata_t *)data
+{
+ _hb_metadata = data;
+ return self;
+}
+
+- (NSString *)releaseDate
+{
+ if (self.hb_metadata->release_date == nil)
+ {
+ return nil;
+ }
+ return @(self.hb_metadata->release_date);
+}
+
+@end
+
@interface HBTitle ()
@property (nonatomic, readonly) hb_title_t *hb_title;
@property (nonatomic, readonly) hb_handle_t *hb_handle;
@property (nonatomic, readwrite, copy) NSString *name;
-
+@property (nonatomic, readwrite) HBMetadata *metadata;
@property (nonatomic, readwrite) NSArray *audioTracks;
@property (nonatomic, readwrite) NSArray *subtitlesTracks;
@property (nonatomic, readwrite) NSArray *chapters;
_hb_title = title;
_hb_handle = handle;
_featured = featured;
+
+ _metadata = [[HBMetadata alloc] initWithMetadata:title->metadata];
}
return self;
}
-- (NSString *)name
+- (NSString *)name
{
if (!_name)
{
chapters:(NSRange)chaptersRange
quality:(double)quality
bitrate:(int)bitrate
- videoCodec:(uint32_t)codec;
+ videoCodec:(uint32_t)codec
+ creationDate:(NSDate *)creationDate;
+ (NSString *)isoCodeForNativeLang:(NSString *)language;
+ (NSString *)iso6392CodeFor:(NSString *)language;
return mediaURL;
}
++ (nullable NSDate *)getReleaseDate:(HBTitle *)title
+{
+ if ([title.metadata.releaseDate length] == 0){
+ return nil;
+ }
+
+ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+ NSString *dt = title.metadata.releaseDate;
+ [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"];
+ NSDate *releaseDate = [dateFormatter dateFromString:dt];
+ return releaseDate;
+}
+
+ (NSString *)automaticNameForJob:(HBJob *)job
{
HBTitle *title = job.title;
+
+ NSDate *releaseDate = [self getReleaseDate:title];
+ if (releaseDate == nil)
+ {
+ NSDictionary* fileAttribs = [[NSFileManager defaultManager] attributesOfItemAtPath:job.fileURL.path error:nil];
+ releaseDate = [fileAttribs objectForKey:NSFileCreationDate];
+ }
// Generate a new file name
NSString *fileName = [HBUtilities automaticNameForSource:title.name
chapters:NSMakeRange(job.range.chapterStart + 1, job.range.chapterStop + 1)
quality:job.video.qualityType ? job.video.quality : 0
bitrate:!job.video.qualityType ? job.video.avgBitrate : 0
- videoCodec:job.video.encoder];
+ videoCodec:job.video.encoder
+ creationDate:releaseDate
+ ];
return fileName;
}
// use the correct extension based on the container
NSString *ext = [self automaticExtForJob:job];
fileName = [fileName stringByAppendingPathExtension:ext];
-
return fileName;
}
quality:(double)quality
bitrate:(int)bitrate
videoCodec:(uint32_t)codec
+ creationDate:(NSDate *)creationDate
{
NSMutableString *name = [[NSMutableString alloc] init];
// The format array contains the tokens as NSString
NSArray *format = [[NSUserDefaults standardUserDefaults] objectForKey:@"HBAutoNamingFormat"];
+ NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
for (NSString *formatKey in format)
{
{
NSDate *date = [NSDate date];
NSString *dateString = nil;
- NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterShortStyle];
[formatter setTimeStyle:NSDateFormatterNoStyle];
dateString = [[formatter stringFromDate:date] stringByReplacingOccurrencesOfString:@"/" withString:@"-"];
else if ([formatKey isEqualToString:@"{Time}"])
{
NSDate *date = [NSDate date];
- NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterNoStyle];
[formatter setTimeStyle:NSDateFormatterShortStyle];
[name appendString:[formatter stringFromDate:date]];
}
+ else if ([formatKey isEqualToString:@"{Creation-Date}"])
+ {
+ NSString *dateString = nil;
+ [formatter setDateStyle:NSDateFormatterShortStyle];
+ [formatter setTimeStyle:NSDateFormatterNoStyle];
+ dateString = [[formatter stringFromDate:creationDate] stringByReplacingOccurrencesOfString:@"/" withString:@"-"];
+ [name appendString:dateString];
+
+ }
+ else if ([formatKey isEqualToString:@"{Creation-Time}"])
+ {
+ [formatter setDateStyle:NSDateFormatterNoStyle];
+ [formatter setTimeStyle:NSDateFormatterShortStyle];
+ [name appendString:[formatter stringFromDate:creationDate]];
+ }
else if ([formatKey isEqualToString:@"{Chapters}"])
{
if (chaptersRange.location == chaptersRange.length)
public const string Quality = "{quality}";\r
\r
/// <summary>\r
- /// The quality.\r
+ /// The creation date of the new output file.\r
/// </summary>\r
public const string Date = "{date}";\r
\r
/// <summary>\r
- /// The quality.\r
+ /// The creation time of the new output file.\r
/// </summary>\r
public const string Time = "{time}";\r
\r
+ /// <summary>\r
+ /// The source creation date.\r
+ /// </summary>\r
+ public const string CretaionDate = "{creation-date}";\r
+\r
+ /// <summary>\r
+ /// The source creation time.\r
+ /// </summary>\r
+ public const string CreationTime = "{creation-time}";\r
+\r
/// <summary>\r
/// The bitrate.\r
/// </summary>\r
using HandBrake.Interop.Interop.Model.Encoding;\r
\r
using HandBrakeWPF.Extensions;\r
+ using HandBrakeWPF.Services.Encode.Model;\r
using HandBrakeWPF.Services.Interfaces;\r
using HandBrakeWPF.Services.Presets.Model;\r
\r
if (chapterFinish != chapterStart && chapterFinish != string.Empty)\r
combinedChapterTag = chapterStart + "-" + chapterFinish;\r
\r
+ // Local method to check if we have a creation time in the meta data. If not, fall back to source file creation time.\r
+ DateTime obtainCreateDateObject()\r
+ {\r
+ var rd = task.MetaData.ReleaseDate;\r
+ if (DateTime.TryParse(rd, out var d))\r
+ {\r
+ return d;\r
+ }\r
+ try\r
+ {\r
+ return File.GetCreationTime(task.Source);\r
+ }\r
+ catch (Exception e)\r
+ {\r
+ if (e is UnauthorizedAccessException ||\r
+ e is PathTooLongException ||\r
+ e is NotSupportedException)\r
+ {\r
+ // Suspect the most likely concerns trying to grab the creation date in which we would want to swallow exception.\r
+ return default(DateTime);\r
+ }\r
+ throw e; \r
+ }\r
+ }\r
+\r
+ var creationDateTime = obtainCreateDateObject();\r
+ string createDate = creationDateTime.Date.ToShortDateString().Replace('/', '-');\r
+ string createTime = creationDateTime.ToString("HH-mm"); \r
+\r
/*\r
* File Name\r
*/\r
.Replace(Constants.Title, dvdTitle)\r
.Replace(Constants.Chapters, combinedChapterTag)\r
.Replace(Constants.Date, DateTime.Now.Date.ToShortDateString().Replace('/', '-'))\r
- .Replace(Constants.Time, DateTime.Now.ToString("HH:mm"));\r
+ .Replace(Constants.Time, DateTime.Now.ToString("HH-mm"))\r
+ .Replace(Constants.CretaionDate, createDate)\r
+ .Replace(Constants.CreationTime, createTime);\r
// .Replace(Constants.Preset, sanitisedPresetName);\r
\r
if (task.VideoEncodeRateType == VideoEncodeRateType.ConstantQuality)\r
\r
return autoNamePath;\r
}\r
-\r
+ \r
/// <summary>\r
/// Check if there is a valid autoname path.\r
/// </summary>\r
return userSettingService.GetUserSetting<string>(UserSettingConstants.AutoNamePath).Trim().StartsWith("{source_path}") ||\r
(userSettingService.GetUserSetting<string>(UserSettingConstants.AutoNamePath).Contains("{source_folder_name}") ||\r
Directory.Exists(userSettingService.GetUserSetting<string>(UserSettingConstants.AutoNamePath).Trim()));\r
- }\r
+ } \r
}\r
}\r
/// Looks up a localized string similar to The format of the output file. In addition to any supported file system character, you can use the following placeholders that will be replaced when you change title or scan a source.\r
///\r
///Live Update Options: {source} {title} {chapters} \r
- ///Non-Live Options: {date} {time} {quality} {bitrate} (These only change if you scan a new source, change title or chapters).\r
+ ///Non-Live Options: {date} {time} {creation-date} {creation-time} {quality} {bitrate} (These only change if you scan a new source, change title or chapters).\r
/// </summary>\r
public static string Options_AdditionalFormatOptions {\r
get {\r
<value>The format of the output file. In addition to any supported file system character, you can use the following placeholders that will be replaced when you change title or scan a source.\r
\r
Live Update Options: {source} {title} {chapters} \r
-Non-Live Options: {date} {time} {quality} {bitrate} (These only change if you scan a new source, change title or chapters)</value>\r
+Non-Live Options: {date} {time} {creation-date} {creation-time} {quality} {bitrate} (These only change if you scan a new source, change title or chapters)</value>\r
</data>\r
<data name="Options_DefaultPathAdditionalParams" xml:space="preserve">\r
<value>Available additional Options: {source_path} or {source_folder_name} \r