mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2026-05-21 16:18:22 -05:00
FF-1173 - updated for new file service
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,501 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using HtmlAgilityPack;
|
||||
using ImageMagick;
|
||||
|
||||
namespace FileFlows.ImageNodes.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Scrapes a website, follows links within a specified depth, and saves images over a certain resolution.
|
||||
/// </summary>
|
||||
public class WebImageScraper : Node
|
||||
{
|
||||
private HashSet<string> savedImages = new ();
|
||||
private static readonly HttpClient httpClient = new HttpClient();
|
||||
private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(5); // Limit to 5 concurrent image downloads
|
||||
|
||||
private int PageCount = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum depth of links to follow.
|
||||
/// </summary>
|
||||
[NumberInt(1)]
|
||||
public int MaxDepth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum width of images to save.
|
||||
/// </summary>
|
||||
[NumberInt(2)]
|
||||
public int MinWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum height of images to save.
|
||||
/// </summary>
|
||||
[NumberInt(3)]
|
||||
public int MinHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of pages to process
|
||||
/// </summary>
|
||||
[NumberInt(4)]
|
||||
public int MaxPages { get; set; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Starts the web scraping process.
|
||||
/// </summary>
|
||||
/// <param name="args">the node parameters used during execution</param>
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
string textFile = args.WorkingFile;
|
||||
if (File.Exists(textFile) == false)
|
||||
{
|
||||
args.Logger?.ELog($"File '{textFile}' does not exist.");
|
||||
return -1; // Indicate failure
|
||||
}
|
||||
|
||||
string[] lines = File.ReadAllLines(textFile);
|
||||
if (lines.Length > 1000)
|
||||
{
|
||||
args.Logger?.WLog("The file contains more than 1000 lines of URLs.");
|
||||
}
|
||||
|
||||
lines = lines.Take(1000).Where(x => string.IsNullOrWhiteSpace(x) == false).Select(x => x.Trim()).ToArray();
|
||||
|
||||
|
||||
var allImageUrls = new List<(string, List<string>)>();
|
||||
foreach (string line in lines) // Limiting to process only 1000 lines
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
|
||||
string path = string.Empty;
|
||||
if (lines.Length > 1)
|
||||
{
|
||||
Uri.TryCreate(line, UriKind.Absolute, out Uri uri);
|
||||
if (uri != null)
|
||||
{
|
||||
string uriPath = uri.AbsolutePath.Trim('/');
|
||||
if (!string.IsNullOrEmpty(uriPath))
|
||||
{
|
||||
string[] segments = uriPath.Split('/');
|
||||
path = segments.Last();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path = string.IsNullOrWhiteSpace(path) ? args.TempPath : Path.Combine(args.TempPath, path);
|
||||
|
||||
PageCount = 0;
|
||||
var imageUrls = ProcessUrlAndGetImageUrls(args.Logger!, line.Trim(), 0, null).GetAwaiter().GetResult();
|
||||
allImageUrls.Add((path, imageUrls));
|
||||
}
|
||||
|
||||
SaveUrlsToFile(allImageUrls.SelectMany(x => x.Item2).ToList(), Path.Combine(args.TempPath, "image_urls.txt")).GetAwaiter().GetResult();
|
||||
int totalDownloaded = DownloadImages(args.Logger!, allImageUrls, args.TempPath).GetAwaiter().GetResult();
|
||||
args.Logger?.ILog($"Total images downloaded: {totalDownloaded}");
|
||||
|
||||
return totalDownloaded > 0 ? 1 : 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a list of URLs to a text file asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="urls">The list of URLs to be saved.</param>
|
||||
/// <param name="fileName">The name of the file to save the URLs.</param>
|
||||
/// <returns>A task representing the asynchronous saving operation.</returns>
|
||||
private async Task SaveUrlsToFile(List<string> urls, string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
await File.WriteAllLinesAsync(fileName, urls);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle file writing error
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads images asynchronously from a list of image URLs.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger implementation for logging.</param>
|
||||
/// <param name="imageUrls">The list of image URLs to download.</param>
|
||||
/// <param name="outputPath">the base path to save the images to</param>
|
||||
/// <returns>The total count of downloaded images.</returns>
|
||||
private async Task<int> DownloadImages(ILogger logger, List<(string path, List<string> urls)> imageUrls, string outputPath)
|
||||
{
|
||||
int totalDownloaded = 0;
|
||||
|
||||
var downloadTasks = new List<Task>();
|
||||
foreach (var imageData in imageUrls)
|
||||
{
|
||||
foreach (var imageUrl in imageData.urls)
|
||||
{
|
||||
downloadTasks.Add(ProcessImageAsync(logger, imageUrl, Path.Combine(outputPath, imageData.path)));
|
||||
}
|
||||
}
|
||||
await Task.WhenAll(downloadTasks);
|
||||
|
||||
totalDownloaded = downloadTasks.Count(task => task.IsCompletedSuccessfully);
|
||||
|
||||
if (totalDownloaded > 1)
|
||||
{
|
||||
var paths = imageUrls.Select(x => x.path).Distinct().ToList();
|
||||
if (paths.Count > 1)
|
||||
{
|
||||
foreach(var path in paths)
|
||||
DeleteDuplicates(logger, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
DeleteDuplicates(logger, outputPath);
|
||||
}
|
||||
}
|
||||
|
||||
return totalDownloaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a URL to extract image URLs and follows links within a specified depth.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger implementation for logging.</param>
|
||||
/// <param name="url">The URL to be processed.</param>
|
||||
/// <param name="depth">The current depth of link following.</param>
|
||||
/// <param name="baseUrl">The base URL for domain comparison.</param>
|
||||
/// <returns>A list of image URLs extracted from the URL and its sub-links.</returns>
|
||||
private async Task<List<string>> ProcessUrlAndGetImageUrls(ILogger logger, string url, int depth, string baseUrl)
|
||||
{
|
||||
var imageUrls = new List<string>();
|
||||
if (++PageCount > MaxPages)
|
||||
return imageUrls;
|
||||
|
||||
if (string.IsNullOrEmpty(baseUrl))
|
||||
{
|
||||
Uri.TryCreate(url, UriKind.Absolute, out Uri uri);
|
||||
baseUrl = uri?.GetLeftPart(UriPartial.Path); // Retrieve URL up to path level
|
||||
}
|
||||
|
||||
// Ensure the base URL doesn't contain query parameters
|
||||
if (!string.IsNullOrEmpty(baseUrl) && baseUrl.Contains("?"))
|
||||
{
|
||||
baseUrl = baseUrl.Substring(0, baseUrl.IndexOf('?'));
|
||||
}
|
||||
|
||||
if (baseUrl == null)
|
||||
return imageUrls;
|
||||
|
||||
baseUrl = baseUrl.TrimEnd('/');
|
||||
|
||||
try
|
||||
{
|
||||
HttpResponseMessage response = await httpClient.GetAsync(url);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
string htmlContent = await response.Content.ReadAsStringAsync();
|
||||
var doc = new HtmlDocument();
|
||||
doc.LoadHtml(htmlContent);
|
||||
|
||||
var currentImageUrls = doc.DocumentNode.SelectNodes("//img[@src]")?
|
||||
.Select(node => node.Attributes["src"]?.Value)
|
||||
.Where(i => string.IsNullOrWhiteSpace(i) == false && IsImageUrl(i))
|
||||
.ToList() ?? new ();
|
||||
|
||||
var dataSrc = doc.DocumentNode.SelectNodes("//*[@data-src]")?
|
||||
.Select(node =>node.Attributes["data-src"]?.Value)
|
||||
.Where(i => string.IsNullOrWhiteSpace(i) == false && IsImageUrl(i))
|
||||
.ToList() ?? new ();
|
||||
currentImageUrls.AddRange(dataSrc);
|
||||
|
||||
var hrefSrc = doc.DocumentNode.SelectNodes("//a[@href]")?
|
||||
.Select(node =>node.Attributes["href"]?.Value)
|
||||
.Where(i => string.IsNullOrWhiteSpace(i) == false && IsImageUrl(i))
|
||||
.ToList() ?? new ();
|
||||
|
||||
currentImageUrls.AddRange(hrefSrc);
|
||||
|
||||
if (currentImageUrls?.Any() == true)
|
||||
{
|
||||
foreach (var imageUrl in currentImageUrls)
|
||||
{
|
||||
if (!savedImages.Contains(imageUrl))
|
||||
{
|
||||
imageUrls.Add(imageUrl);
|
||||
savedImages.Add(imageUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var links = doc.DocumentNode.SelectNodes("//a[@href]")
|
||||
?.Select(a => a.Attributes["href"].Value)
|
||||
.Where(a => string.IsNullOrWhiteSpace(a) == false && IsSameDomain(a, baseUrl))
|
||||
.ToList();
|
||||
|
||||
if (links != null && depth < MaxDepth)
|
||||
{
|
||||
foreach (var link in links)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(link) || link.StartsWith("#"))
|
||||
continue;
|
||||
|
||||
// Convert relative URLs to absolute URLs
|
||||
var absoluteLink = ConvertToAbsoluteUrl(baseUrl, link);
|
||||
|
||||
if (baseUrl == absoluteLink)
|
||||
continue;
|
||||
|
||||
if (absoluteLink.StartsWith(baseUrl) == false)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
var urlsFromLink = await ProcessUrlAndGetImageUrls(logger, absoluteLink, depth + 1, baseUrl);
|
||||
imageUrls.AddRange(urlsFromLink);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.WLog($"Failed to download images from URL: {url}. Status code: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.ELog($"Error processing URL: {url}. {ex.Message}");
|
||||
}
|
||||
|
||||
return imageUrls;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts a relative URL to an absolute URL based on the base URL.
|
||||
/// </summary>
|
||||
/// <param name="baseUrl">The base URL used for domain comparison.</param>
|
||||
/// <param name="link">The URL to be converted to an absolute URL.</param>
|
||||
/// <returns>The absolute URL.</returns>
|
||||
private string ConvertToAbsoluteUrl(string baseUrl, string link)
|
||||
{
|
||||
if (Uri.IsWellFormedUriString(link, UriKind.Absolute))
|
||||
{
|
||||
// Absolute URL
|
||||
return link;
|
||||
}
|
||||
else if (link.StartsWith("/"))
|
||||
{
|
||||
// Root-relative URL
|
||||
if (!baseUrl.EndsWith("/"))
|
||||
{
|
||||
// Ensure the base URL ends with a "/"
|
||||
baseUrl += "/";
|
||||
}
|
||||
|
||||
// Combine the base URL and root-relative link manually
|
||||
return $"{baseUrl.TrimEnd('/')}/{link.TrimStart('/')}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Path-relative URL
|
||||
var baseUri = new Uri(baseUrl);
|
||||
var combinedUri = new Uri(baseUri, link);
|
||||
return combinedUri.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the provided URL is a valid image URL based on common image extensions.
|
||||
/// </summary>
|
||||
/// <param name="imageUrl">The URL to be checked.</param>
|
||||
/// <returns>True if the URL ends with common image extensions; otherwise, false.</returns>
|
||||
private bool IsImageUrl(string imageUrl)
|
||||
{
|
||||
if (imageUrl == null || imageUrl.EndsWith("1px.png") || imageUrl.EndsWith("svg"))
|
||||
return false;
|
||||
// Check if the URL ends with common image extensions (e.g., jpg, png, gif, etc.)
|
||||
var imageExtensionPattern = @"\.(jpg|jpeg|jpe|png|gif|bmp|webp)$";
|
||||
var regex = new Regex(imageExtensionPattern, RegexOptions.IgnoreCase);
|
||||
return regex.IsMatch(imageUrl);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a URL is from the same domain as the specified base URL.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL to be checked.</param>
|
||||
/// <param name="baseUrl">The base URL used for comparison.</param>
|
||||
/// <returns>True if the URL is from the same domain; otherwise, false.</returns>
|
||||
private bool IsSameDomain(string url, string baseUrl)
|
||||
{
|
||||
if (url?.StartsWith("#") == true)
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(url) || url.ToLowerInvariant().StartsWith("http") == false)
|
||||
return true;
|
||||
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri? uri) &&
|
||||
Uri.TryCreate(baseUrl, UriKind.Absolute, out Uri? baseUri))
|
||||
{
|
||||
return uri.Host == baseUri.Host;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handling case when URLs are not in a valid format
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves an image from the specified URL if it meets the minimum width and height criteria.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger implementation for logging.</param>
|
||||
/// <param name="imageUrl">The URL of the image to be saved.</param>
|
||||
/// <param name="outputPath">the base path to save the images to</param>
|
||||
/// <returns>true if the image downloaded and the resolution was acceptable</returns>
|
||||
private async Task<bool> ProcessImageAsync(ILogger logger, string imageUrl, string outputPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
await semaphore.WaitAsync(); // Wait for a semaphore slot
|
||||
|
||||
HttpResponseMessage response = await httpClient.GetAsync(imageUrl);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
byte[] imageData = await response.Content.ReadAsByteArrayAsync();
|
||||
|
||||
using (MagickImage image = new MagickImage(imageData))
|
||||
{
|
||||
if (image.Width >= MinWidth || image.Height >= MinHeight)
|
||||
{
|
||||
var fileName = Path.GetFileName(imageUrl);
|
||||
var savePath = Path.Combine(outputPath, fileName);
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(outputPath) == false)
|
||||
Directory.CreateDirectory(outputPath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
await image.WriteAsync(savePath); // Save the image directly
|
||||
savedImages.Add(imageUrl);
|
||||
logger.ILog($"Saved image: {fileName}");
|
||||
return true; // Signal successful download
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.ELog($"Failed to download image from {imageUrl}. Status code: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.ELog($"Error saving image from {imageUrl}: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release(); // Release semaphore slot
|
||||
}
|
||||
|
||||
return false; // Signal unsuccessful download
|
||||
}
|
||||
/// <summary>
|
||||
/// Deletes duplicate images within a directory.
|
||||
/// </summary>
|
||||
/// <param name="logger">the logger to write logging to</param>
|
||||
/// <param name="directoryPath">The path to the directory containing images.</param>
|
||||
private void DeleteDuplicates(ILogger logger, string directoryPath)
|
||||
{
|
||||
|
||||
Dictionary<string, List<string>> hashes = new Dictionary<string, List<string>>();
|
||||
|
||||
// Load all image files in the directory
|
||||
string[] imageFiles = Directory.GetFiles(directoryPath, "*.*");
|
||||
|
||||
foreach (string filePath in imageFiles)
|
||||
{
|
||||
using (var image = new MagickImage(filePath))
|
||||
{
|
||||
image.Resize(300, 300);
|
||||
|
||||
// Calculate histogram
|
||||
int[] histogram = CalculateHistogram(image);
|
||||
|
||||
// Convert histogram to a string for hash storage
|
||||
string hash = string.Join(",", histogram);
|
||||
|
||||
if (!hashes.ContainsKey(hash))
|
||||
{
|
||||
hashes[hash] = new List<string>();
|
||||
}
|
||||
|
||||
hashes[hash].Add(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete duplicates, keeping the largest file
|
||||
foreach (var kvp in hashes)
|
||||
{
|
||||
List<string> duplicateFiles = kvp.Value;
|
||||
|
||||
if (duplicateFiles.Count > 1)
|
||||
{
|
||||
string largestFile = GetLargestFile(duplicateFiles);
|
||||
|
||||
foreach (string file in duplicateFiles)
|
||||
{
|
||||
if (file != largestFile)
|
||||
{
|
||||
File.Delete(file);
|
||||
logger?.ILog($"Deleted duplicate: {file}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the histogram of an image
|
||||
static int[] CalculateHistogram(MagickImage image)
|
||||
{
|
||||
int[] histogram = new int[256]; // Assuming grayscale images
|
||||
|
||||
using (var pixels = image.GetPixels())
|
||||
{
|
||||
foreach (var pixel in pixels)
|
||||
{
|
||||
var grayscale = (int)(pixel.GetChannel((int)Channels.Red) * 0.3 +
|
||||
pixel.GetChannel((int)Channels.Green) * 0.59 +
|
||||
pixel.GetChannel((int)Channels.Blue) * 0.11);
|
||||
|
||||
histogram[grayscale]++;
|
||||
}
|
||||
}
|
||||
|
||||
return histogram;
|
||||
}
|
||||
|
||||
private string GetLargestFile(List<string> files)
|
||||
{
|
||||
long maxSize = 0;
|
||||
string largestFile = "";
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(file);
|
||||
if (fileInfo.Length > maxSize)
|
||||
{
|
||||
maxSize = fileInfo.Length;
|
||||
largestFile = file;
|
||||
}
|
||||
}
|
||||
|
||||
return largestFile;
|
||||
}
|
||||
}
|
||||
@@ -158,12 +158,12 @@ public class FfmpegBuilderExecutor: FfmpegBuilderNode
|
||||
if(ffArgs.Any(x => x.Contains("_qsv")))
|
||||
{
|
||||
// use qsv decoder
|
||||
startArgs.AddRange(new[] { "-hwaccel", "qsv" });
|
||||
startArgs.AddRange(new[] { "-hwaccel", "qsv", "-hwaccel_output_format", "qsv" });
|
||||
}
|
||||
else if(ffArgs.Any(x => x.Contains("_nvenc")))
|
||||
{
|
||||
// use nvidia decoder
|
||||
startArgs.AddRange(new[] { "-hwaccel", "cuda" });
|
||||
startArgs.AddRange(new[] { "-hwaccel", "cuda", "-hwaccel_output_format", "cuda" });
|
||||
}
|
||||
else if (HardwareDecoding)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,10 @@ global using System.Linq;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Text.RegularExpressions;
|
||||
global using System.ComponentModel.DataAnnotations;
|
||||
global using System.ComponentModel;
|
||||
global using System.Threading;
|
||||
global using System.Threading.Tasks;
|
||||
global using FileFlows.Plugin;
|
||||
global using FileFlows.Plugin.Attributes;
|
||||
global using System.ComponentModel;
|
||||
|
||||
global using FileFlows.Plugin.Helpers;
|
||||
global using System.IO;
|
||||
@@ -5,7 +5,7 @@ namespace FileFlows.VideoNodes.Helpers;
|
||||
/// </summary>
|
||||
class VaapiHelper
|
||||
{
|
||||
internal static bool VaapiLinux => OperatingSystem.IsLinux() && File.Exists(VaapiRenderDevice);
|
||||
internal static bool VaapiLinux => OperatingSystem.IsLinux() && System.IO.File.Exists(VaapiRenderDevice);
|
||||
|
||||
internal const string VaapiRenderDevice = "/dev/dri/renderD128";
|
||||
}
|
||||
@@ -55,12 +55,12 @@ public interface ITrackStreamSelectionNode
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionEquals(nameof(Stream), "Video", inverse: true)]
|
||||
[ConditionEquals(nameof(StreamType), "Video", inverse: true)]
|
||||
[TextVariable(14)]
|
||||
public string Language { get; set; }
|
||||
|
||||
|
||||
[ConditionEquals(nameof(Stream), "Audio")]
|
||||
[ConditionEquals(nameof(StreamType), "Audio")]
|
||||
[NumberFloat(17)]
|
||||
public float Channels { get; set; }
|
||||
}
|
||||
|
||||
@@ -35,7 +35,13 @@ public class VideoHasErrors: VideoNode
|
||||
/// <returns>the output to call next</returns>
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
var result = ValidateFile(FFMPEG, args.FileName);
|
||||
var file = args.FileService.GetLocalPath(args.WorkingFile);
|
||||
if (file.IsFailed)
|
||||
{
|
||||
args.Logger?.ILog("Failed to get file: " + file.Error);
|
||||
return -1;
|
||||
}
|
||||
var result = ValidateFile(FFMPEG, file);
|
||||
if (result.NoErrors)
|
||||
return 2;
|
||||
|
||||
@@ -54,10 +60,10 @@ public class VideoHasErrors: VideoNode
|
||||
/// </returns>
|
||||
public static (bool NoErrors, string Log) ValidateFile(string ffmpegPath, string filename)
|
||||
{
|
||||
if (File.Exists(ffmpegPath) == false)
|
||||
if (System.IO.File.Exists(ffmpegPath) == false)
|
||||
return (false, "FFmpeg does not exist at the specified path.");
|
||||
|
||||
if (File.Exists(filename) == false)
|
||||
if (System.IO.File.Exists(filename) == false)
|
||||
return (false, "The input file does not exist.");
|
||||
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
|
||||
@@ -4,11 +4,7 @@ namespace VideoNodes.Tests;
|
||||
|
||||
using FileFlows.VideoNodes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
|
||||
[TestClass]
|
||||
public class AudioExtractorTests : TestBase
|
||||
@@ -39,6 +35,7 @@ public class AudioExtractorTests : TestBase
|
||||
var log = logger.ToString();
|
||||
Assert.AreEqual(1, output);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AudioExtractor_Mp3_English()
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using FileFlows.VideoNodes.FfmpegBuilderNodes;
|
||||
using FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using VideoNodes.Tests;
|
||||
using System.IO;
|
||||
|
||||
namespace FileFlows.VideoNodes.Tests.FfmpegBuilderTests;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using FileFlows.VideoNodes.FfmpegBuilderNodes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using VideoNodes.Tests;
|
||||
using System.IO;
|
||||
|
||||
namespace FileFlows.VideoNodes.Tests.FfmpegBuilderTests;
|
||||
|
||||
@@ -40,7 +41,7 @@ public class FfmpegBuilder_VideoEncode_VideoEncodeTests: TestBase
|
||||
int result = ffExecutor.Execute(args);
|
||||
string log = logger.ToString();
|
||||
if(args.WorkingFile.StartsWith(args.TempPath))
|
||||
File.Move(args.WorkingFile, Path.Combine(args.TempPath, outfile), true);
|
||||
File.Move(args.WorkingFile, FileHelper.Combine(args.TempPath, outfile), true);
|
||||
Assert.AreEqual(1, result);
|
||||
return (result, log);
|
||||
}
|
||||
|
||||
@@ -5,10 +5,7 @@ namespace VideoNodes.Tests
|
||||
using FileFlows.VideoNodes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
|
||||
[TestClass]
|
||||
public class SubtitleExtractorTests: TestBase
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Runtime.InteropServices;
|
||||
using FileFlows.VideoNodes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Text.Json;
|
||||
using System.IO;
|
||||
|
||||
namespace VideoNodes.Tests;
|
||||
|
||||
@@ -20,11 +21,11 @@ public abstract class TestBase
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
if (File.Exists("../../../test.settings.dev.json"))
|
||||
if (System.IO.File.Exists("../../../test.settings.dev.json"))
|
||||
{
|
||||
LoadSettings("../../../test.settings.dev.json");
|
||||
}
|
||||
else if (File.Exists("../../../test.settings.json"))
|
||||
else if (System.IO.File.Exists("../../../test.settings.json"))
|
||||
{
|
||||
LoadSettings("../../../test.settings.json");
|
||||
}
|
||||
|
||||
@@ -51,12 +51,12 @@ public class VideoInfoHelper
|
||||
|
||||
var vi = new VideoInfo();
|
||||
vi.FileName = filename;
|
||||
if (File.Exists(filename) == false)
|
||||
if (System.IO.File.Exists(filename) == false)
|
||||
{
|
||||
logger.ELog("File not found: " + filename);
|
||||
return vi;
|
||||
}
|
||||
if (string.IsNullOrEmpty(ffMpegExe) || File.Exists(ffMpegExe) == false)
|
||||
if (string.IsNullOrEmpty(ffMpegExe) || System.IO.File.Exists(ffMpegExe) == false)
|
||||
{
|
||||
logger.ELog("FFmpeg not found: " + (ffMpegExe ?? "not passed in"));
|
||||
return vi;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
|
||||
<FileVersion>1.1.1.528</FileVersion>
|
||||
|
||||
@@ -13,6 +13,39 @@
|
||||
{
|
||||
public override int Outputs => 2;
|
||||
|
||||
private string GetEdlFile(NodeParameters args)
|
||||
{
|
||||
string edlFile = args.WorkingFile.Substring(0, args.WorkingFile.LastIndexOf(".", StringComparison.Ordinal) + 1) +
|
||||
"edl";
|
||||
if (args.FileService.FileIsLocal(edlFile))
|
||||
{
|
||||
if (System.IO.File.Exists(edlFile))
|
||||
return edlFile;
|
||||
|
||||
return args.WorkingFile.Substring(0,
|
||||
args.WorkingFile.LastIndexOf(".", StringComparison.Ordinal) + 1) + "edl";
|
||||
}
|
||||
if (args.FileService.FileExists(edlFile).Is(true) == false)
|
||||
{
|
||||
edlFile = args.WorkingFile.Substring(0,
|
||||
args.WorkingFile.LastIndexOf(".", StringComparison.Ordinal) + 1) + "edl";
|
||||
|
||||
if (args.FileService.FileExists(edlFile).Is(true) == false)
|
||||
return string.Empty;
|
||||
|
||||
}
|
||||
|
||||
var result = args.FileService.GetLocalPath(edlFile);
|
||||
if (result.IsFailed)
|
||||
{
|
||||
args.Logger.ELog("Failed to download edl file locally: " + result.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
VideoInfo videoInfo = GetVideoInfo(args);
|
||||
@@ -21,16 +54,15 @@
|
||||
float totalTime = (float)videoInfo.VideoStreams[0].Duration.TotalSeconds;
|
||||
|
||||
|
||||
string edlFile = args.WorkingFile.Substring(0, args.WorkingFile.LastIndexOf(".") + 1) + "edl";
|
||||
if(File.Exists(edlFile) == false)
|
||||
edlFile = args.WorkingFile.Substring(0, args.WorkingFile.LastIndexOf(".") + 1) + "edl";
|
||||
if (File.Exists(edlFile) == false)
|
||||
string edlFile = GetEdlFile(args);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(edlFile) || System.IO.File.Exists(edlFile) == false)
|
||||
{
|
||||
args.Logger?.ILog("No EDL file found for file");
|
||||
return 2;
|
||||
}
|
||||
|
||||
string text = File.ReadAllText(edlFile) ?? string.Empty;
|
||||
string text = System.IO.File.ReadAllText(edlFile) ?? string.Empty;
|
||||
float last = -1;
|
||||
List<BreakPoint> breakPoints = new List<BreakPoint>();
|
||||
foreach(string line in text.Split(new string[] { "\r\n", "\n", "\r"}, StringSplitOptions.RemoveEmptyEntries))
|
||||
@@ -63,7 +95,7 @@
|
||||
|
||||
float segStart = 0;
|
||||
string extension = args.WorkingFile.Substring(args.WorkingFile.LastIndexOf(".") + 1);
|
||||
string segmentPrefix = Path.Combine(args.TempPath, Guid.NewGuid().ToString())+"_";
|
||||
string segmentPrefix = System.IO.Path.Combine(args.TempPath, Guid.NewGuid().ToString())+"_";
|
||||
int count = 0;
|
||||
List<string> segmentsInfo = new List<string>();
|
||||
foreach (BreakPoint bp in breakPoints)
|
||||
@@ -85,7 +117,7 @@
|
||||
// stitch file back together
|
||||
string concatList = segmentPrefix + "concatlist.txt";
|
||||
string concatListContents = String.Join(Environment.NewLine, segments.Select(x => $"file '{x}'"));
|
||||
File.WriteAllText(concatList, concatListContents);
|
||||
System.IO.File.WriteAllText(concatList, concatListContents);
|
||||
|
||||
args.Logger?.ILog("====================================================");
|
||||
foreach (var str in segmentsInfo)
|
||||
@@ -112,7 +144,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(segment);
|
||||
System.IO.File.Delete(segment);
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace FileFlows.VideoNodes
|
||||
Encoder.AtTime += AtTimeEvent;
|
||||
|
||||
if (string.IsNullOrEmpty(outputFile))
|
||||
outputFile = Path.Combine(args.TempPath, Guid.NewGuid().ToString() + "." + extension);
|
||||
outputFile = System.IO.Path.Combine(args.TempPath, Guid.NewGuid() + "." + extension);
|
||||
|
||||
if (TotalTime.TotalMilliseconds == 0)
|
||||
{
|
||||
@@ -220,7 +220,7 @@ namespace FileFlows.VideoNodes
|
||||
else
|
||||
{
|
||||
// linux, crude method, look for nvidia in the /dev dir
|
||||
var dir = new DirectoryInfo("/dev");
|
||||
var dir = new System.IO.DirectoryInfo("/dev");
|
||||
if (dir.Exists == false)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -129,12 +129,15 @@ public class SubtitleExtractor : EncodingNode
|
||||
}
|
||||
else
|
||||
{
|
||||
var file = new FileInfo(args.FileName);
|
||||
output = file.FullName[..file.FullName.LastIndexOf(file.Extension, StringComparison.Ordinal)];
|
||||
string fileFullname = args.FileName;
|
||||
string fileExtension = FileHelper.GetExtension(fileFullname);
|
||||
output = fileFullname[..fileFullname.LastIndexOf(fileExtension, StringComparison.Ordinal)];
|
||||
|
||||
output = output +
|
||||
(string.IsNullOrWhiteSpace(subTrack.Language) == false ? "." + subTrack.Language : "") +
|
||||
(string.IsNullOrWhiteSpace(subTrack.Language) == false ? subTrack.Language : "") +
|
||||
(i == 0 ? "" : "." + i);
|
||||
|
||||
output = output.Replace("..", ".");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace FileFlows.VideoNodes
|
||||
Args.Logger.ELog("FFmpeg variable not found.");
|
||||
return string.Empty;
|
||||
}
|
||||
var fileInfo = new FileInfo(ffmpeg);
|
||||
var fileInfo = new System.IO.FileInfo(ffmpeg);
|
||||
if (fileInfo.Exists == false)
|
||||
{
|
||||
Args.Logger.ELog("FFmpeg does not exist: " + ffmpeg);
|
||||
|
||||
Reference in New Issue
Block a user