Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
# Changelog

## v1.1.1 — Stable

- When “Save all stills” is enabled but the selected source has no usable Sample Images, saving now skips `extrafanart` automatically instead of interrupting the entire operation.
- The completion status and local log explicitly report that the still-image output was skipped.

## v1.1.0 — Stable

- Promotes RC1 without functional changes after the complete automated gate and final Windows acceptance passed.
- Adds an explicit faster cross-volume transfer mode while retaining full target-side SHA-256 verification as the safe default.
- Makes successful online searches select their new artwork by default while preserving local images as selectable candidates.
- Accepts a single-movie ID folder through drag and drop, with non-recursive and ambiguity-blocking input rules.

## v1.1.0-rc1 — Release candidate

- Freezes the accepted dev1–dev3 behavior without additional functional changes.
- Carries forward optional cross-volume verification, post-search online artwork defaults, and single-movie ID-folder drag and drop.
- Requires the complete offline automated gate and a final compact Windows acceptance pass before stable promotion.

## v1.1.0-dev3 — ID folder drag and drop

- Accepts either one supported movie file or one single-movie ID folder through drag and drop.
- Resolves only a folder's top-level movie file so metadata subfolders and unrelated nested media are never scanned.
- Rejects empty or multi-movie folders with a clear localized message instead of guessing which movie to open.

## v1.1.0-dev2 — Online artwork becomes the post-search default

- Switches poster and fanart to the preferred successful online source after a search, matching the existing metadata-field behavior.
- Keeps previously loaded local images and a manually chosen cover in the artwork source menu so the user can switch back explicitly.
- Leaves initial movie loading unchanged: local sidecar images remain the default until a successful search supplies new artwork.

## v1.1.0-dev1 — Optional cross-volume verification

- Keeps full target-side SHA-256 verification as the safe default for cross-volume and UNC transfers.
- Adds an explicit fast-transfer option that skips the second complete target read and checks copy completion plus file size only.
- Preserves target-side staging, late-conflict protection, cancellation, source retirement after commit, and rollback in both modes.
- Shows the selected transfer policy in the target hint and save preview, with a clear at-your-own-risk warning for fast mode.
- Persists the choice only when the user enables remembered save preferences and migrates schema-v4 settings to full verification.
- Adds four-language UI copy plus offline preference, transaction, preview, and UI regression coverage.

## v1.0.0 — First public stable release

- Establishes the accepted v0.9.0 feature set as the first public, feature-frozen release.
Expand Down
6 changes: 3 additions & 3 deletions JavMetaLite.App/JavMetaLite.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
<Nullable>enable</Nullable>
<AssemblyName>JavMetaLite</AssemblyName>
<RootNamespace>JavMetaLite.App</RootNamespace>
<Version>1.0.0</Version>
<FileVersion>1.0.0.0</FileVersion>
<InformationalVersion>1.0.0</InformationalVersion>
<Version>1.1.1</Version>
<FileVersion>1.1.1.0</FileVersion>
<InformationalVersion>1.1.1</InformationalVersion>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Authors>Noredge</Authors>
<Company>Noredge</Company>
Expand Down
15 changes: 11 additions & 4 deletions JavMetaLite.App/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@
<Image x:Name="PosterImage" Stretch="Uniform" Margin="8" />
<StackPanel x:Name="DropHint" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="24">
<TextBlock Text="+" FontSize="38" Foreground="#5E7CA6" HorizontalAlignment="Center" />
<TextBlock Text="{DynamicResource Main.DropVideo}" FontSize="16" FontWeight="SemiBold" HorizontalAlignment="Center" Margin="0,8,0,0" />
<TextBlock Text="{DynamicResource Main.DropInput}" FontSize="16" FontWeight="SemiBold" HorizontalAlignment="Center" Margin="0,8,0,0" TextAlignment="Center" TextWrapping="Wrap" />
<TextBlock Text="MP4 · MKV · AVI · WMV" Foreground="{StaticResource MutedBrush}" FontSize="12" HorizontalAlignment="Center" Margin="0,6,0,0" />
</StackPanel>
</Grid>
Expand Down Expand Up @@ -479,7 +479,7 @@
<CheckBox x:Name="RememberPreferencesCheckBox"
Content="{DynamicResource Main.RememberPreferences}"
IsChecked="False"
ToolTip="{DynamicResource Main.RememberPreferencesTooltip}" />
ToolTip="{DynamicResource Main.RememberPreferencesTooltipVerification}" />
</WrapPanel>
<WrapPanel Margin="0,10,0,0">
<TextBlock Text="{DynamicResource Main.TargetLocation}" Foreground="{StaticResource MutedBrush}" FontSize="11" FontWeight="SemiBold" Margin="0,0,16,0" VerticalAlignment="Center" />
Expand All @@ -493,6 +493,13 @@
<ComboBoxItem Content="{DynamicResource Main.Target.SourceNumberFolder}" Tag="SourceNumberFolder" />
<ComboBoxItem Content="{DynamicResource Main.Target.CustomRootNumberFolder}" Tag="CustomRootNumberFolder" />
</ComboBox>
<CheckBox x:Name="SkipCrossVolumeVerificationCheckBox"
Content="{DynamicResource Main.SkipCrossVolumeVerification}"
IsChecked="False"
Visibility="Collapsed"
Checked="TargetOption_Changed"
Unchecked="TargetOption_Changed"
ToolTip="{DynamicResource Main.SkipCrossVolumeVerificationTooltip}" />
</WrapPanel>
<Grid x:Name="CustomTargetPanel" Margin="0,10,0,0" Visibility="Collapsed">
<Grid.ColumnDefinitions>
Expand Down Expand Up @@ -550,7 +557,7 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Ellipse x:Name="StatusDot" Width="8" Height="8" Fill="#72E3A6" VerticalAlignment="Center" Margin="0,0,9,0" />
<TextBlock x:Name="StatusText" Grid.Column="1" Text="{DynamicResource Main.StatusReady}" Foreground="{StaticResource MutedBrush}" VerticalAlignment="Center" />
<TextBlock x:Name="StatusText" Grid.Column="1" Text="{DynamicResource Main.StatusReadyInput}" Foreground="{StaticResource MutedBrush}" VerticalAlignment="Center" />
<Button x:Name="CancelOperationButton"
Grid.Column="2"
Content="{DynamicResource Main.CancelOperation}"
Expand All @@ -563,7 +570,7 @@
BorderBrush="#85363D"
Visibility="Collapsed" />
<Button Grid.Column="3" Content="{DynamicResource Common.OpenLogs}" Click="OpenLogs_Click" Padding="10,4" Margin="12,0" FontSize="11" FontWeight="Normal" Background="#1B2330" />
<TextBlock Grid.Column="4" Text="{DynamicResource Main.Version}" Foreground="#607086" FontSize="11" VerticalAlignment="Center" />
<TextBlock Grid.Column="4" Text="{DynamicResource Main.VersionPreview}" Foreground="#607086" FontSize="11" VerticalAlignment="Center" />
</Grid>
</Border>
</Grid>
Expand Down
78 changes: 69 additions & 9 deletions JavMetaLite.App/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ internal MainWindow(AppPreferencesStore preferencesStore)
_uiInitialized = true;
ApplyMetadata(_metadata, []);
RefreshTargetLocationUi();
AppLog.Info("JavMetaLite v1.0.0 启动");
AppLog.Info("JavMetaLite v1.1.1 启动");
}

internal void LoadPreferences()
Expand Down Expand Up @@ -110,6 +110,7 @@ internal void LoadPreferences()
AppLog.Info(
$"已恢复保存偏好 target={result.Preferences.TargetMode} " +
$"rename={result.Preferences.RenameVideo} directOverwrite={result.Preferences.DirectSaveOverwrite} " +
$"crossVolumeVerification={result.Preferences.CrossVolumeVerification} " +
$"customRoot={result.Preferences.CustomRootDirectory}");
SetStatus(
result.Preferences.DirectSaveOverwrite
Expand All @@ -123,6 +124,8 @@ private void ApplyPreferences(AppPreferences preferences)
{
ApplyLanguagePreference(preferences.UiLanguage);
DirectSaveOverwriteCheckBox.IsChecked = preferences.DirectSaveOverwrite;
SkipCrossVolumeVerificationCheckBox.IsChecked =
preferences.CrossVolumeVerification is CrossVolumeVerificationMode.FileSizeOnly;
RememberPreferencesCheckBox.IsChecked = preferences.RememberSavePreferences;
WriteNfoCheckBox.IsChecked = preferences.WriteNfo;
DownloadPosterCheckBox.IsChecked = preferences.DownloadPoster;
Expand Down Expand Up @@ -158,6 +161,7 @@ private AppPreferences CapturePreferences()
UiLanguage = LocalizationService.CurrentLanguageCode,
RememberSavePreferences = RememberPreferencesCheckBox.IsChecked == true,
DirectSaveOverwrite = DirectSaveOverwriteCheckBox.IsChecked == true,
CrossVolumeVerification = GetCrossVolumeVerificationMode(),
TargetMode = GetSelectedTargetMode(),
CustomRootDirectory = string.IsNullOrWhiteSpace(CustomRootTextBox.Text)
? _lastValidCustomRootDirectory
Expand Down Expand Up @@ -269,15 +273,49 @@ private async void ChooseFile_Click(object sender, RoutedEventArgs e)

private void Window_DragOver(object sender, DragEventArgs e)
{
e.Effects = TryGetDroppedVideo(e.Data, out _) ? DragDropEffects.Copy : DragDropEffects.None;
e.Effects = TryGetSingleDroppedPath(e.Data, out var path) &&
(VideoFileSupport.IsSupportedExistingFile(path) || Directory.Exists(path))
? DragDropEffects.Copy
: DragDropEffects.None;
e.Handled = true;
}

private async void Window_Drop(object sender, DragEventArgs e)
{
if (TryGetDroppedVideo(e.Data, out var path))
if (!TryGetSingleDroppedPath(e.Data, out var path))
{
await SelectVideoAsync(path!);
return;
}

try
{
var resolution = VideoFileSupport.ResolveInputPath(path);
if (resolution.Success)
{
if (Directory.Exists(path))
{
AppLog.Info($"从拖入番号文件夹解析影片 folder={path} video={resolution.VideoPath}");
}
await SelectVideoAsync(resolution.VideoPath!);
}
else
{
ShowError(LocalizationService.Get(resolution.Status switch
{
VideoInputPathStatus.FolderHasNoVideo => "Error.FolderHasNoVideo",
VideoInputPathStatus.FolderHasMultipleVideos => "Error.FolderHasMultipleVideos",
_ => "Error.UnsupportedVideo"
}));
}
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException)
{
AppLog.Warning($"无法读取拖入的影片文件夹 path={path}", exception);
ShowError(LocalizationService.Get("Error.ReadVideoFolder", exception.Message));
}
finally
{
e.Handled = true;
}
}

Expand Down Expand Up @@ -500,6 +538,11 @@ await RunBusyAsync(LocalizationService.Get("Status.Saving"), async () =>
? LocalizationService.Get("Status.SidecarsMigrated")
: LocalizationService.Get("Status.NoChanges")
: string.Join(LocalizationService.Get("Common.ListSeparator"), outputs);
if (options.DownloadExtrafanart && result.Outputs.ExtrafanartPaths.Count == 0)
{
outputSummary += LocalizationService.Get("Common.ListSeparator") +
LocalizationService.Get("Status.ExtrafanartSkipped");
}
SetStatus(LocalizationService.Get("Status.SaveComplete", outputSummary, fanartNote, moveNote), true);
});
}
Expand Down Expand Up @@ -746,7 +789,13 @@ private OrganizationOptions GetOrganizationOptions() =>
RenameVideoCheckBox.IsChecked == true,
GetSelectedTargetMode() is OrganizationTargetMode.CustomRootNumberFolder
? CustomRootTextBox.Text
: null);
: null,
GetCrossVolumeVerificationMode());

private CrossVolumeVerificationMode GetCrossVolumeVerificationMode() =>
SkipCrossVolumeVerificationCheckBox.IsChecked == true
? CrossVolumeVerificationMode.FileSizeOnly
: CrossVolumeVerificationMode.FullSha256;

private OrganizationTargetMode GetSelectedTargetMode()
{
Expand All @@ -771,6 +820,7 @@ private void RefreshTargetLocationUi()
private void RefreshTargetLocationPreview()
{
_targetConfigurationError = null;
SkipCrossVolumeVerificationCheckBox.Visibility = Visibility.Collapsed;
var customMode = GetSelectedTargetMode() is OrganizationTargetMode.CustomRootNumberFolder;
if (customMode &&
!string.IsNullOrWhiteSpace(CustomRootTextBox.Text) &&
Expand Down Expand Up @@ -808,8 +858,14 @@ private void RefreshTargetLocationPreview()
TargetPathHintText.Text = LocalizationService.Get("Main.FinalVideo", pathPlan.TargetVideoPath);
if (pathPlan.RequiresVerifiedCopy)
{
TargetPathHintText.Text += Environment.NewLine + LocalizationService.Get("Main.SafeCopy");
TargetPathHintText.Foreground = new SolidColorBrush(Color.FromRgb(141, 184, 255));
SkipCrossVolumeVerificationCheckBox.Visibility = Visibility.Visible;
var fastCopy = GetCrossVolumeVerificationMode() is CrossVolumeVerificationMode.FileSizeOnly;
TargetPathHintText.Text += Environment.NewLine + LocalizationService.Get(
fastCopy ? "Main.FastCopy" : "Main.SafeCopy");
TargetPathHintText.Foreground = new SolidColorBrush(
fastCopy
? Color.FromRgb(255, 209, 138)
: Color.FromRgb(141, 184, 255));
}
else
{
Expand Down Expand Up @@ -1036,6 +1092,9 @@ private MovieMetadata ApplyOnlineSources(
IReadOnlyList<MovieMetadata> onlineSources)
{
var retainedManualCandidates = CaptureManualCandidates();
_preferredArtworkSourceName = MetadataCandidateSource
.FromMetadata(preferredOnlineMetadata)
.Name;
if (_localSourceMetadata is null)
{
ApplyMetadataCore(preferredOnlineMetadata, onlineSources, retainedManualCandidates);
Expand Down Expand Up @@ -1725,6 +1784,7 @@ private static string GetLocalizedTransactionProgress(FileTransactionProgress up
FileTransactionStage.VerifyingMovie => LocalizationService.Get("Progress.Verifying"),
FileTransactionStage.Committing => LocalizationService.Get("Progress.Committing"),
FileTransactionStage.RetiringSource => LocalizationService.Get("Progress.RetiringSource"),
FileTransactionStage.RetiringSourceFast => LocalizationService.Get("Progress.RetiringSourceFast"),
FileTransactionStage.Completed => LocalizationService.Get("Progress.Completed"),
_ => update.Message
};
Expand Down Expand Up @@ -1834,7 +1894,7 @@ private void OpenLogs_Click(object sender, RoutedEventArgs e)
}
}

private static bool TryGetDroppedVideo(IDataObject data, out string? path)
private static bool TryGetSingleDroppedPath(IDataObject data, out string? path)
{
path = null;
if (!data.GetDataPresent(DataFormats.FileDrop) || data.GetData(DataFormats.FileDrop) is not string[] files || files.Length != 1)
Expand All @@ -1843,7 +1903,7 @@ private static bool TryGetDroppedVideo(IDataObject data, out string? path)
}

path = files[0];
return VideoFileSupport.IsSupportedExistingFile(path);
return true;
}

private void Window_Closing(object? sender, CancelEventArgs e)
Expand Down
Loading