From 5857fcfae327b3afbc9787002aef920ccdfec8ce Mon Sep 17 00:00:00 2001 From: Andy <88590076+AAndyProgram@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:36:36 +0300 Subject: [PATCH] 2025.8.30.0 YT Add video trim Fix downloading error Add artist name when downloading audio Embed chapters SCrawler Add correct handling of 'webp' files API.Redgifs: hide 'Responser.Save' error --- Changelog.md | 20 +- SCrawler.YouTube/Base/Structures.vb | 30 ++ SCrawler.YouTube/Base/YouTubeSettings.vb | 25 +- .../Controls/ChaptersForm.Designer.vb | 158 ++++++ SCrawler.YouTube/Controls/ChaptersForm.resx | 129 +++++ SCrawler.YouTube/Controls/ChaptersForm.vb | 66 +++ .../Controls/TrimOptionForm.Designer.vb | 274 +++++++++++ SCrawler.YouTube/Controls/TrimOptionForm.resx | 138 ++++++ SCrawler.YouTube/Controls/TrimOptionForm.vb | 79 +++ .../Controls/VideoOptionsForm.Designer.vb | 24 +- .../Controls/VideoOptionsForm.resx | 458 +++++++++--------- SCrawler.YouTube/Controls/VideoOptionsForm.vb | 3 + .../Controls/VideoOptionsTrimForm.Designer.vb | 166 +++++++ .../Controls/VideoOptionsTrimForm.resx | 135 ++++++ .../Controls/VideoOptionsTrimForm.vb | 167 +++++++ SCrawler.YouTube/Declarations.vb | 7 +- SCrawler.YouTube/Downloader/VideoListForm.vb | 1 + SCrawler.YouTube/My Project/AssemblyInfo.vb | 4 +- SCrawler.YouTube/Objects/Track.vb | 5 + .../Objects/YouTubeMediaContainerBase.vb | 77 ++- SCrawler.YouTube/SCrawler.YouTube.vbproj | 27 ++ .../My Project/AssemblyInfo.vb | 4 +- SCrawler.sln | 14 + SCrawler/API/Base/UserDataBase.vb | 25 +- SCrawler/API/Redgifs/SiteSettings.vb | 14 +- SCrawler/Download/Feed/FeedMedia.vb | 86 +++- SCrawler/My Project/AssemblyInfo.vb | 4 +- SCrawler/SCrawler.vbproj | 6 + SCrawler/UserImage.vb | 53 ++ 29 files changed, 1920 insertions(+), 279 deletions(-) create mode 100644 SCrawler.YouTube/Controls/ChaptersForm.Designer.vb create mode 100644 SCrawler.YouTube/Controls/ChaptersForm.resx create mode 100644 SCrawler.YouTube/Controls/ChaptersForm.vb create mode 100644 SCrawler.YouTube/Controls/TrimOptionForm.Designer.vb create mode 100644 SCrawler.YouTube/Controls/TrimOptionForm.resx create mode 100644 SCrawler.YouTube/Controls/TrimOptionForm.vb create mode 100644 SCrawler.YouTube/Controls/VideoOptionsTrimForm.Designer.vb create mode 100644 SCrawler.YouTube/Controls/VideoOptionsTrimForm.resx create mode 100644 SCrawler.YouTube/Controls/VideoOptionsTrimForm.vb diff --git a/Changelog.md b/Changelog.md index ffe50d7..cd0a5bd 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,21 @@ +# 2025.8.30.0 + +*2025-08-30* + +- Added + - YouTube: + - **video trim** *(button `Trim`)* + - embed chapters into video file + - add artist name when downloading audio + - Correct handling of `webp` files + - Minor improvements +- Updated + - yt-dlp up to version **2025.08.27** + - gallery-dl up to version **1.30.5** +- Fixed + - **YouTube: downloading error** + - Minor bugs + # 2025.8.1.0 *2025-08-01* @@ -8,7 +26,7 @@ - Twitter: **[large profile option](https://github.com/AAndyProgram/SCrawler/wiki/Settings#twitter-user-settings) in user settings** - Minor improvements - Updated - - yt-dlp up to version **2025.27.21** + - yt-dlp up to version **2025.07.21** - gallery-dl up to version **1.30.2** - Fixed - Reddit: in some cases crossposts don't download diff --git a/SCrawler.YouTube/Base/Structures.vb b/SCrawler.YouTube/Base/Structures.vb index 7ad2e99..3fef50a 100644 --- a/SCrawler.YouTube/Base/Structures.vb +++ b/SCrawler.YouTube/Base/Structures.vb @@ -136,4 +136,34 @@ Namespace API.YouTube.Base End If End Function End Structure + Public Structure TrimOption : Implements IComparable(Of TrimOption) + Public Name As String + Public Start As Integer + Public ReadOnly Property StartTime As TimeSpan + Get + Return TimeSpan.FromSeconds(Start) + End Get + End Property + Public [End] As Integer + Public ReadOnly Property EndTime As TimeSpan + Get + Return TimeSpan.FromSeconds([End]) + End Get + End Property + Public Shared Widening Operator CType(ByVal t As TrimOption) As String + Return t.ToString + End Operator + Public Overrides Function ToString() As String + Dim v$ = $"{Start} - {[End]}" + If Not Name.IsEmptyString Then v = $"[{v}] - {Name}" + Return v + End Function + Public Overrides Function Equals(ByVal Obj As Object) As Boolean + Try : With DirectCast(Obj, TrimOption) : Return Start = .Start And [End] = .End : End With : Catch : End Try + Return False + End Function + Private Function CompareTo(ByVal Other As TrimOption) As Integer Implements IComparable(Of TrimOption).CompareTo + Return Start.CompareTo(Other.Start) + End Function + End Structure End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Base/YouTubeSettings.vb b/SCrawler.YouTube/Base/YouTubeSettings.vb index 82428e7..cf42f1d 100644 --- a/SCrawler.YouTube/Base/YouTubeSettings.vb +++ b/SCrawler.YouTube/Base/YouTubeSettings.vb @@ -319,8 +319,11 @@ Namespace API.YouTube.Base Description("Convert non-AVC codecs (eg 'VP9') to AVC. Not recommended due to high CPU usage!")> Public ReadOnly Property DefaultVideoConvertNonAVC As XMLValue(Of Boolean) + Description("Embed thumbnail in the video as cover art. Default: false.")> Public ReadOnly Property DefaultVideoEmbedThumbnail As XMLValue(Of Boolean) + + Public ReadOnly Property DefaultVideoEmbedChapters As XMLValue(Of Boolean) Public ReadOnly Property DefaultVideoIncludeNullSize As XMLValue(Of Boolean) @@ -496,6 +499,26 @@ Namespace API.YouTube.Base Description("Additional format for downloading subtitles. This means that all subtitles will be converted to the formats you choose and saved as separate files.")> Public ReadOnly Property DefaultSubtitlesFormatAddit As XMLValuesCollection(Of String) #End Region +#Region "Trim" + + Public ReadOnly Property TrimDeleteOriginalFile As XMLValue(Of Boolean) + + Public ReadOnly Property TrimAddTrimmedFilesToM3U8 As XMLValue(Of Boolean) + + Public ReadOnly Property TrimSeparateFolder As XMLValue(Of Boolean) + Friend Const TrimSeparateFolderNameDefault As String = "Trim" + + Public ReadOnly Property TrimSeparateFolderName As XMLValue(Of String) +#End Region +#Region "Errors" + + Public ReadOnly Property ErrorsIgnore As XMLValue(Of Boolean) +#End Region #End Region #Region "Initializer" Public Sub New() diff --git a/SCrawler.YouTube/Controls/ChaptersForm.Designer.vb b/SCrawler.YouTube/Controls/ChaptersForm.Designer.vb new file mode 100644 index 0000000..0904f8b --- /dev/null +++ b/SCrawler.YouTube/Controls/ChaptersForm.Designer.vb @@ -0,0 +1,158 @@ +' Copyright (C) Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.YouTube.Controls + + Partial Friend Class ChaptersForm : Inherits System.Windows.Forms.Form + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Dim CONTAINER_MAIN As System.Windows.Forms.ToolStripContainer + Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel + Dim TP_BUTTONS As System.Windows.Forms.TableLayoutPanel + Me.BTT_ALL = New System.Windows.Forms.Button() + Me.BTT_NONE = New System.Windows.Forms.Button() + Me.BTT_INVERT = New System.Windows.Forms.Button() + Me.LIST_CHAPTERS = New System.Windows.Forms.CheckedListBox() + CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer() + TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + TP_BUTTONS = New System.Windows.Forms.TableLayoutPanel() + CONTAINER_MAIN.ContentPanel.SuspendLayout() + CONTAINER_MAIN.SuspendLayout() + TP_MAIN.SuspendLayout() + TP_BUTTONS.SuspendLayout() + Me.SuspendLayout() + ' + 'CONTAINER_MAIN + ' + ' + 'CONTAINER_MAIN.ContentPanel + ' + CONTAINER_MAIN.ContentPanel.Controls.Add(TP_MAIN) + CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(384, 361) + CONTAINER_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + CONTAINER_MAIN.LeftToolStripPanelVisible = False + CONTAINER_MAIN.Location = New System.Drawing.Point(0, 0) + CONTAINER_MAIN.Name = "CONTAINER_MAIN" + CONTAINER_MAIN.RightToolStripPanelVisible = False + CONTAINER_MAIN.Size = New System.Drawing.Size(384, 361) + CONTAINER_MAIN.TabIndex = 0 + CONTAINER_MAIN.TopToolStripPanelVisible = False + ' + 'TP_MAIN + ' + TP_MAIN.ColumnCount = 1 + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_MAIN.Controls.Add(TP_BUTTONS, 0, 1) + TP_MAIN.Controls.Add(Me.LIST_CHAPTERS, 0, 0) + TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + TP_MAIN.Location = New System.Drawing.Point(0, 0) + TP_MAIN.Name = "TP_MAIN" + TP_MAIN.RowCount = 2 + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30.0!)) + TP_MAIN.Size = New System.Drawing.Size(384, 361) + TP_MAIN.TabIndex = 0 + ' + 'TP_BUTTONS + ' + TP_BUTTONS.ColumnCount = 3 + TP_BUTTONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!)) + TP_BUTTONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!)) + TP_BUTTONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!)) + TP_BUTTONS.Controls.Add(Me.BTT_ALL, 0, 0) + TP_BUTTONS.Controls.Add(Me.BTT_NONE, 1, 0) + TP_BUTTONS.Controls.Add(Me.BTT_INVERT, 2, 0) + TP_BUTTONS.Dock = System.Windows.Forms.DockStyle.Fill + TP_BUTTONS.Location = New System.Drawing.Point(0, 331) + TP_BUTTONS.Margin = New System.Windows.Forms.Padding(0) + TP_BUTTONS.Name = "TP_BUTTONS" + TP_BUTTONS.RowCount = 1 + TP_BUTTONS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_BUTTONS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_BUTTONS.Size = New System.Drawing.Size(384, 30) + TP_BUTTONS.TabIndex = 1 + ' + 'BTT_ALL + ' + Me.BTT_ALL.Dock = System.Windows.Forms.DockStyle.Fill + Me.BTT_ALL.Location = New System.Drawing.Point(3, 3) + Me.BTT_ALL.Name = "BTT_ALL" + Me.BTT_ALL.Size = New System.Drawing.Size(122, 24) + Me.BTT_ALL.TabIndex = 0 + Me.BTT_ALL.Text = "All" + Me.BTT_ALL.UseVisualStyleBackColor = True + ' + 'BTT_NONE + ' + Me.BTT_NONE.Dock = System.Windows.Forms.DockStyle.Fill + Me.BTT_NONE.Location = New System.Drawing.Point(131, 3) + Me.BTT_NONE.Name = "BTT_NONE" + Me.BTT_NONE.Size = New System.Drawing.Size(122, 24) + Me.BTT_NONE.TabIndex = 1 + Me.BTT_NONE.Text = "None" + Me.BTT_NONE.UseVisualStyleBackColor = True + ' + 'BTT_INVERT + ' + Me.BTT_INVERT.Dock = System.Windows.Forms.DockStyle.Fill + Me.BTT_INVERT.Location = New System.Drawing.Point(259, 3) + Me.BTT_INVERT.Name = "BTT_INVERT" + Me.BTT_INVERT.Size = New System.Drawing.Size(122, 24) + Me.BTT_INVERT.TabIndex = 2 + Me.BTT_INVERT.Text = "Invert" + Me.BTT_INVERT.UseVisualStyleBackColor = True + ' + 'LIST_CHAPTERS + ' + Me.LIST_CHAPTERS.Dock = System.Windows.Forms.DockStyle.Fill + Me.LIST_CHAPTERS.FormattingEnabled = True + Me.LIST_CHAPTERS.Location = New System.Drawing.Point(3, 3) + Me.LIST_CHAPTERS.Name = "LIST_CHAPTERS" + Me.LIST_CHAPTERS.Size = New System.Drawing.Size(378, 325) + Me.LIST_CHAPTERS.TabIndex = 0 + ' + 'ChaptersForm + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.ClientSize = New System.Drawing.Size(384, 361) + Me.Controls.Add(CONTAINER_MAIN) + Me.KeyPreview = True + Me.MinimizeBox = False + Me.MinimumSize = New System.Drawing.Size(400, 400) + Me.Name = "ChaptersForm" + Me.ShowIcon = False + Me.ShowInTaskbar = False + Me.Text = "Chapters" + CONTAINER_MAIN.ContentPanel.ResumeLayout(False) + CONTAINER_MAIN.ResumeLayout(False) + CONTAINER_MAIN.PerformLayout() + TP_MAIN.ResumeLayout(False) + TP_BUTTONS.ResumeLayout(False) + Me.ResumeLayout(False) + + End Sub + + Private WithEvents BTT_ALL As Button + Private WithEvents BTT_NONE As Button + Private WithEvents BTT_INVERT As Button + Private WithEvents LIST_CHAPTERS As CheckedListBox + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/ChaptersForm.resx b/SCrawler.YouTube/Controls/ChaptersForm.resx new file mode 100644 index 0000000..ac07c27 --- /dev/null +++ b/SCrawler.YouTube/Controls/ChaptersForm.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/ChaptersForm.vb b/SCrawler.YouTube/Controls/ChaptersForm.vb new file mode 100644 index 0000000..d6b1e7b --- /dev/null +++ b/SCrawler.YouTube/Controls/ChaptersForm.vb @@ -0,0 +1,66 @@ +' Copyright (C) Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Forms.Toolbars +Imports SCrawler.API.YouTube.Base +Imports SCrawler.API.YouTube.Objects +Namespace API.YouTube.Controls + Friend Class ChaptersForm + Private WithEvents MyDefs As DefaultFormOptions + Private ReadOnly Property MyData As YouTubeMediaContainerBase + Private ReadOnly Property MyDataSelected As List(Of TrimOption) + Friend ReadOnly Property MyResult As List(Of TrimOption) + Friend Sub New(ByRef data As YouTubeMediaContainerBase, ByVal selected As IEnumerable(Of TrimOption)) + InitializeComponent() + MyDefs = New DefaultFormOptions(Me, MyYouTubeSettings.DesignXml) + MyData = data + MyDataSelected = New List(Of TrimOption) + MyDataSelected.ListAddList(selected) + MyResult = New List(Of TrimOption) + End Sub + Private Sub ChaptersForm_Load(sender As Object, e As EventArgs) Handles Me.Load + With MyDefs + .MyViewInitialize() + .AddOkCancelToolbar() + + With LIST_CHAPTERS + .BeginUpdate() + .Items.AddRange(MyData.Chapters.Cast(Of Object).ToArray) + If MyDataSelected.Count > 0 Then + For i% = 0 To .Items.Count - 1 : .SetItemChecked(i, MyDataSelected.Contains(MyData.Chapters(i))) : Next + End If + .EndUpdate() + End With + + .EndLoaderOperations() + End With + End Sub + Private Sub ChaptersForm_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed + MyResult.Clear() + MyDataSelected.Clear() + End Sub + Private Sub MyDefs_ButtonOkClick(ByVal Sender As Object, ByVal e As KeyHandleEventArgs) Handles MyDefs.ButtonOkClick + With LIST_CHAPTERS + For i% = 0 To .Items.Count - 1 + If .GetItemChecked(i) Then MyResult.Add(MyData.Chapters(i)) + Next + End With + MyDefs.CloseForm() + End Sub + Private Sub BTT_ALL_NONE_INVERT_Click(sender As Object, e As EventArgs) Handles BTT_ALL.Click, BTT_NONE.Click, BTT_INVERT.Click + Dim v As Boolean = sender Is BTT_ALL + Dim isInvert As Boolean = sender Is BTT_INVERT + With LIST_CHAPTERS + .BeginUpdate() + For i% = 0 To .Items.Count - 1 : .SetItemChecked(i, If(isInvert, Not .GetItemChecked(i), v)) : Next + .EndUpdate() + End With + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/TrimOptionForm.Designer.vb b/SCrawler.YouTube/Controls/TrimOptionForm.Designer.vb new file mode 100644 index 0000000..cc4c5f4 --- /dev/null +++ b/SCrawler.YouTube/Controls/TrimOptionForm.Designer.vb @@ -0,0 +1,274 @@ +' Copyright (C) Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.YouTube.Controls + + Partial Friend Class TrimOptionForm : Inherits System.Windows.Forms.Form + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Dim CONTAINER_MAIN As System.Windows.Forms.ToolStripContainer + Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel + Dim ActionButton1 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(TrimOptionForm)) + Dim TP_OPTIONS As System.Windows.Forms.TableLayoutPanel + Me.TXT_NAME = New PersonalUtilities.Forms.Controls.TextBoxExtended() + Me.TP_TIME_TIME = New System.Windows.Forms.TableLayoutPanel() + Me.TXT_FROM_DATE = New PersonalUtilities.Forms.Controls.TextBoxExtended() + Me.TXT_TO_DATE = New PersonalUtilities.Forms.Controls.TextBoxExtended() + Me.OPT_INT = New System.Windows.Forms.RadioButton() + Me.OPT_TIME = New System.Windows.Forms.RadioButton() + Me.TP_TIME_INT = New System.Windows.Forms.TableLayoutPanel() + Me.TXT_FROM_INT = New PersonalUtilities.Forms.Controls.TextBoxExtended() + Me.TXT_TO_INT = New PersonalUtilities.Forms.Controls.TextBoxExtended() + CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer() + TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + TP_OPTIONS = New System.Windows.Forms.TableLayoutPanel() + CONTAINER_MAIN.ContentPanel.SuspendLayout() + CONTAINER_MAIN.SuspendLayout() + TP_MAIN.SuspendLayout() + CType(Me.TXT_NAME, System.ComponentModel.ISupportInitialize).BeginInit() + TP_OPTIONS.SuspendLayout() + Me.TP_TIME_TIME.SuspendLayout() + CType(Me.TXT_FROM_DATE, System.ComponentModel.ISupportInitialize).BeginInit() + CType(Me.TXT_TO_DATE, System.ComponentModel.ISupportInitialize).BeginInit() + Me.TP_TIME_INT.SuspendLayout() + CType(Me.TXT_FROM_INT, System.ComponentModel.ISupportInitialize).BeginInit() + CType(Me.TXT_TO_INT, System.ComponentModel.ISupportInitialize).BeginInit() + Me.SuspendLayout() + ' + 'CONTAINER_MAIN + ' + ' + 'CONTAINER_MAIN.ContentPanel + ' + CONTAINER_MAIN.ContentPanel.Controls.Add(TP_MAIN) + CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(334, 112) + CONTAINER_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + CONTAINER_MAIN.LeftToolStripPanelVisible = False + CONTAINER_MAIN.Location = New System.Drawing.Point(0, 0) + CONTAINER_MAIN.Name = "CONTAINER_MAIN" + CONTAINER_MAIN.RightToolStripPanelVisible = False + CONTAINER_MAIN.Size = New System.Drawing.Size(334, 112) + CONTAINER_MAIN.TabIndex = 0 + CONTAINER_MAIN.TopToolStripPanelVisible = False + ' + 'TP_MAIN + ' + TP_MAIN.ColumnCount = 1 + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_MAIN.Controls.Add(Me.TXT_NAME, 0, 0) + TP_MAIN.Controls.Add(TP_OPTIONS, 0, 1) + TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + TP_MAIN.Location = New System.Drawing.Point(0, 0) + TP_MAIN.Name = "TP_MAIN" + TP_MAIN.RowCount = 2 + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.Size = New System.Drawing.Size(334, 112) + TP_MAIN.TabIndex = 0 + ' + 'TXT_NAME + ' + ActionButton1.BackgroundImage = CType(resources.GetObject("ActionButton1.BackgroundImage"), System.Drawing.Image) + ActionButton1.Name = "Clear" + ActionButton1.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear + Me.TXT_NAME.Buttons.Add(ActionButton1) + Me.TXT_NAME.CaptionText = "Name" + Me.TXT_NAME.CaptionToolTipText = "Section name" + Me.TXT_NAME.CaptionWidth = 50.0R + Me.TXT_NAME.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_NAME.Location = New System.Drawing.Point(3, 3) + Me.TXT_NAME.Name = "TXT_NAME" + Me.TXT_NAME.Size = New System.Drawing.Size(328, 22) + Me.TXT_NAME.TabIndex = 0 + ' + 'TP_OPTIONS + ' + TP_OPTIONS.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] + TP_OPTIONS.ColumnCount = 2 + TP_OPTIONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 30.0!)) + TP_OPTIONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_OPTIONS.Controls.Add(Me.TP_TIME_TIME, 1, 1) + TP_OPTIONS.Controls.Add(Me.OPT_INT, 0, 0) + TP_OPTIONS.Controls.Add(Me.OPT_TIME, 0, 1) + TP_OPTIONS.Controls.Add(Me.TP_TIME_INT, 1, 0) + TP_OPTIONS.Dock = System.Windows.Forms.DockStyle.Fill + TP_OPTIONS.Location = New System.Drawing.Point(3, 31) + TP_OPTIONS.Name = "TP_OPTIONS" + TP_OPTIONS.RowCount = 3 + TP_OPTIONS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + TP_OPTIONS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + TP_OPTIONS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_OPTIONS.Size = New System.Drawing.Size(328, 78) + TP_OPTIONS.TabIndex = 1 + ' + 'TP_TIME_TIME + ' + Me.TP_TIME_TIME.ColumnCount = 2 + Me.TP_TIME_TIME.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + Me.TP_TIME_TIME.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + Me.TP_TIME_TIME.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + Me.TP_TIME_TIME.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + Me.TP_TIME_TIME.Controls.Add(Me.TXT_FROM_DATE, 0, 0) + Me.TP_TIME_TIME.Controls.Add(Me.TXT_TO_DATE, 1, 0) + Me.TP_TIME_TIME.Dock = System.Windows.Forms.DockStyle.Fill + Me.TP_TIME_TIME.Location = New System.Drawing.Point(32, 27) + Me.TP_TIME_TIME.Margin = New System.Windows.Forms.Padding(0) + Me.TP_TIME_TIME.Name = "TP_TIME_TIME" + Me.TP_TIME_TIME.RowCount = 1 + Me.TP_TIME_TIME.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_TIME_TIME.Size = New System.Drawing.Size(295, 25) + Me.TP_TIME_TIME.TabIndex = 3 + ' + 'TXT_FROM_DATE + ' + Me.TXT_FROM_DATE.CaptionPadding = New System.Windows.Forms.Padding(0, 0, 6, 0) + Me.TXT_FROM_DATE.CaptionText = "From" + Me.TXT_FROM_DATE.CaptionWidth = 40.0R + Me.TXT_FROM_DATE.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_FROM_DATE.Location = New System.Drawing.Point(3, 3) + Me.TXT_FROM_DATE.Name = "TXT_FROM_DATE" + Me.TXT_FROM_DATE.PlaceholderEnabled = True + Me.TXT_FROM_DATE.PlaceholderText = "h:mm:ss" + Me.TXT_FROM_DATE.Size = New System.Drawing.Size(141, 22) + Me.TXT_FROM_DATE.TabIndex = 0 + ' + 'TXT_TO_DATE + ' + Me.TXT_TO_DATE.CaptionPadding = New System.Windows.Forms.Padding(0, 0, 6, 0) + Me.TXT_TO_DATE.CaptionText = "To" + Me.TXT_TO_DATE.CaptionWidth = 40.0R + Me.TXT_TO_DATE.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_TO_DATE.Location = New System.Drawing.Point(150, 3) + Me.TXT_TO_DATE.Name = "TXT_TO_DATE" + Me.TXT_TO_DATE.PlaceholderEnabled = True + Me.TXT_TO_DATE.PlaceholderText = "h:mm:ss" + Me.TXT_TO_DATE.Size = New System.Drawing.Size(142, 22) + Me.TXT_TO_DATE.TabIndex = 1 + ' + 'OPT_INT + ' + Me.OPT_INT.AutoSize = True + Me.OPT_INT.CheckAlign = System.Drawing.ContentAlignment.MiddleCenter + Me.OPT_INT.Dock = System.Windows.Forms.DockStyle.Fill + Me.OPT_INT.Location = New System.Drawing.Point(4, 4) + Me.OPT_INT.Name = "OPT_INT" + Me.OPT_INT.Size = New System.Drawing.Size(24, 19) + Me.OPT_INT.TabIndex = 0 + Me.OPT_INT.TabStop = True + Me.OPT_INT.UseVisualStyleBackColor = True + ' + 'OPT_TIME + ' + Me.OPT_TIME.AutoSize = True + Me.OPT_TIME.CheckAlign = System.Drawing.ContentAlignment.MiddleCenter + Me.OPT_TIME.Dock = System.Windows.Forms.DockStyle.Fill + Me.OPT_TIME.Location = New System.Drawing.Point(4, 30) + Me.OPT_TIME.Name = "OPT_TIME" + Me.OPT_TIME.Size = New System.Drawing.Size(24, 19) + Me.OPT_TIME.TabIndex = 1 + Me.OPT_TIME.TabStop = True + Me.OPT_TIME.UseVisualStyleBackColor = True + ' + 'TP_TIME_INT + ' + Me.TP_TIME_INT.ColumnCount = 2 + Me.TP_TIME_INT.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + Me.TP_TIME_INT.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + Me.TP_TIME_INT.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + Me.TP_TIME_INT.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + Me.TP_TIME_INT.Controls.Add(Me.TXT_FROM_INT, 0, 0) + Me.TP_TIME_INT.Controls.Add(Me.TXT_TO_INT, 1, 0) + Me.TP_TIME_INT.Dock = System.Windows.Forms.DockStyle.Fill + Me.TP_TIME_INT.Location = New System.Drawing.Point(32, 1) + Me.TP_TIME_INT.Margin = New System.Windows.Forms.Padding(0) + Me.TP_TIME_INT.Name = "TP_TIME_INT" + Me.TP_TIME_INT.RowCount = 1 + Me.TP_TIME_INT.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_TIME_INT.Size = New System.Drawing.Size(295, 25) + Me.TP_TIME_INT.TabIndex = 2 + ' + 'TXT_FROM_INT + ' + Me.TXT_FROM_INT.CaptionPadding = New System.Windows.Forms.Padding(0, 0, 6, 0) + Me.TXT_FROM_INT.CaptionText = "From" + Me.TXT_FROM_INT.CaptionWidth = 40.0R + Me.TXT_FROM_INT.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_FROM_INT.Location = New System.Drawing.Point(3, 3) + Me.TXT_FROM_INT.Name = "TXT_FROM_INT" + Me.TXT_FROM_INT.Size = New System.Drawing.Size(141, 22) + Me.TXT_FROM_INT.TabIndex = 0 + ' + 'TXT_TO_INT + ' + Me.TXT_TO_INT.CaptionPadding = New System.Windows.Forms.Padding(0, 0, 6, 0) + Me.TXT_TO_INT.CaptionText = "To" + Me.TXT_TO_INT.CaptionWidth = 40.0R + Me.TXT_TO_INT.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_TO_INT.Location = New System.Drawing.Point(150, 3) + Me.TXT_TO_INT.Name = "TXT_TO_INT" + Me.TXT_TO_INT.Size = New System.Drawing.Size(142, 22) + Me.TXT_TO_INT.TabIndex = 1 + ' + 'TrimOptionForm + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.ClientSize = New System.Drawing.Size(334, 112) + Me.Controls.Add(CONTAINER_MAIN) + Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle + Me.KeyPreview = True + Me.MaximizeBox = False + Me.MaximumSize = New System.Drawing.Size(350, 151) + Me.MinimizeBox = False + Me.MinimumSize = New System.Drawing.Size(350, 151) + Me.Name = "TrimOptionForm" + Me.ShowIcon = False + Me.ShowInTaskbar = False + Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide + Me.Text = "Trim option" + CONTAINER_MAIN.ContentPanel.ResumeLayout(False) + CONTAINER_MAIN.ResumeLayout(False) + CONTAINER_MAIN.PerformLayout() + TP_MAIN.ResumeLayout(False) + CType(Me.TXT_NAME, System.ComponentModel.ISupportInitialize).EndInit() + TP_OPTIONS.ResumeLayout(False) + TP_OPTIONS.PerformLayout() + Me.TP_TIME_TIME.ResumeLayout(False) + CType(Me.TXT_FROM_DATE, System.ComponentModel.ISupportInitialize).EndInit() + CType(Me.TXT_TO_DATE, System.ComponentModel.ISupportInitialize).EndInit() + Me.TP_TIME_INT.ResumeLayout(False) + CType(Me.TXT_FROM_INT, System.ComponentModel.ISupportInitialize).EndInit() + CType(Me.TXT_TO_INT, System.ComponentModel.ISupportInitialize).EndInit() + Me.ResumeLayout(False) + + End Sub + + Private WithEvents TXT_NAME As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents OPT_INT As RadioButton + Private WithEvents OPT_TIME As RadioButton + Private WithEvents TP_TIME_INT As TableLayoutPanel + Private WithEvents TP_TIME_TIME As TableLayoutPanel + Private WithEvents TXT_FROM_DATE As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents TXT_TO_DATE As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents TXT_FROM_INT As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents TXT_TO_INT As PersonalUtilities.Forms.Controls.TextBoxExtended + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/TrimOptionForm.resx b/SCrawler.YouTube/Controls/TrimOptionForm.resx new file mode 100644 index 0000000..f01c2d4 --- /dev/null +++ b/SCrawler.YouTube/Controls/TrimOptionForm.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + vgAADr4B6kKxwAAAAHpJREFUOE+1kVEKgDAMQ3e2/e+MXqpn6W/HxM7SpkIVB4HxSMKiTUTaFwVQ1X25 + DjMfSxskHBYsAxHJkjUjHgrUNMY4peaMPxb03rcZMVhgn2oDKAwn+L0aROH/Cny4NAGFSx8x+10ZDwV2 + gt+LOCxQsw1nPBS8VQBVTTzyhrdZSUm7AAAAAElFTkSuQmCC + + + + False + + \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/TrimOptionForm.vb b/SCrawler.YouTube/Controls/TrimOptionForm.vb new file mode 100644 index 0000000..8d07b15 --- /dev/null +++ b/SCrawler.YouTube/Controls/TrimOptionForm.vb @@ -0,0 +1,79 @@ +' Copyright (C) Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Forms.Toolbars +Imports SCrawler.API.YouTube.Base +Namespace API.YouTube.Controls + Friend Class TrimOptionForm + Private WithEvents MyDefs As DefaultFormOptions + Friend Property MyTrimOption As TrimOption + Private ReadOnly FieldsDateProvider As New CustomProvider(Function(v) AConvert(Of TimeSpan)(v, Nothing, EDP.ReturnValue)) + Private ReadOnly TCE As New ErrorsDescriber(False, False, False, New TimeSpan) + Friend Sub New(Optional ByVal Opt As TrimOption = Nothing) + InitializeComponent() + MyDefs = New DefaultFormOptions(Me, MyYouTubeSettings.DesignXml) + MyTrimOption = Opt + End Sub + Private Sub TrimOptionForm_Load(sender As Object, e As EventArgs) Handles Me.Load + Try + With MyDefs + .MyViewInitialize() + .AddOkCancelToolbar() + + TXT_NAME.Text = MyTrimOption.Name + TXT_FROM_INT.Text = MyTrimOption.Start + TXT_TO_INT.Text = MyTrimOption.End + + OPT_INT.Checked = True + + .MyFieldsCheckerE = New FieldsChecker + With .MyFieldsCheckerE + .AddControl(Of Integer)(TXT_FROM_INT, "From") + .AddControl(Of Integer)(TXT_TO_INT, "To") + .AddControl(Of String)(TXT_FROM_DATE, "From (time)",, FieldsDateProvider) + .AddControl(Of String)(TXT_TO_DATE, "To (time)",, FieldsDateProvider) + .EndLoaderOperations() + End With + + .EndLoaderOperations() + End With + Catch ex As Exception + MyDefs.InvokeLoaderError(ex) + End Try + End Sub + Private Sub MyDefs_ButtonOkClick(ByVal Sender As Object, ByVal e As KeyHandleEventArgs) Handles MyDefs.ButtonOkClick + If MyDefs.MyFieldsChecker.AllParamsOK Then + MyTrimOption = New TrimOption With { + .Name = TXT_NAME.Text, + .Start = AConvert(Of Integer)(TXT_FROM_INT.Text, 0), + .[End] = AConvert(Of Integer)(TXT_TO_INT.Text, 0) + } + MyDefs.CloseForm() + End If + End Sub + Private Sub OPT_INT_TIME_CheckedChanged(sender As Object, e As EventArgs) Handles OPT_INT.CheckedChanged, OPT_TIME.CheckedChanged + TP_TIME_INT.Enabled = OPT_INT.Checked + TP_TIME_TIME.Enabled = OPT_TIME.Checked + End Sub + Private _TextHandlersEnabled As Boolean = True + Private Sub TXT_FROM_TO_TextChanged(sender As Object, e As EventArgs) Handles TXT_FROM_INT.ActionOnTextChanged, TXT_TO_INT.ActionOnTextChanged, + TXT_FROM_DATE.ActionOnTextChanged, TXT_TO_DATE.ActionOnTextChanged + If _TextHandlersEnabled Then + _TextHandlersEnabled = False + Select Case DirectCast(sender, Control).Name + Case TXT_FROM_INT.Name : TXT_FROM_DATE.Value = AConvert(Of String)(TimeSpan.FromSeconds(AConvert(Of Integer)(TXT_FROM_INT.Text, 0)), TimeToStringProviderH) + Case TXT_TO_INT.Name : TXT_TO_DATE.Value = AConvert(Of String)(TimeSpan.FromSeconds(AConvert(Of Integer)(TXT_TO_INT.Text, 0)), TimeToStringProviderH) + Case TXT_FROM_DATE.Name : TXT_FROM_INT.Text = CInt(CType(AConvert(Of TimeSpan)(TXT_FROM_DATE.Text, TCE), TimeSpan).TotalSeconds) + Case TXT_TO_DATE.Name : TXT_TO_INT.Text = CInt(CType(AConvert(Of TimeSpan)(TXT_TO_DATE.Text, TCE), TimeSpan).TotalSeconds) + End Select + _TextHandlersEnabled = True + End If + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/VideoOptionsForm.Designer.vb b/SCrawler.YouTube/Controls/VideoOptionsForm.Designer.vb index 569e768..97fbdbe 100644 --- a/SCrawler.YouTube/Controls/VideoOptionsForm.Designer.vb +++ b/SCrawler.YouTube/Controls/VideoOptionsForm.Designer.vb @@ -73,6 +73,7 @@ Namespace API.YouTube.Controls Me.OPT_VIDEO = New System.Windows.Forms.RadioButton() Me.OPT_AUDIO = New System.Windows.Forms.RadioButton() Me.LBL_AUDIO_CODEC = New System.Windows.Forms.Label() + Me.BTT_TRIM = New System.Windows.Forms.Button() Me.TXT_FPS = New PersonalUtilities.Forms.Controls.TextBoxExtended() Me.TXT_AUDIO_BITRATE = New PersonalUtilities.Forms.Controls.TextBoxExtended() Me.TP_HEADER_BASE = New System.Windows.Forms.TableLayoutPanel() @@ -545,20 +546,32 @@ Namespace API.YouTube.Controls Me.LBL_AUDIO_CODEC.TextAlign = System.Drawing.ContentAlignment.MiddleRight TT_MAIN.SetToolTip(Me.LBL_AUDIO_CODEC, "Output Audio Codec") ' + 'BTT_TRIM + ' + Me.BTT_TRIM.Dock = System.Windows.Forms.DockStyle.Fill + Me.BTT_TRIM.Location = New System.Drawing.Point(541, 3) + Me.BTT_TRIM.Name = "BTT_TRIM" + Me.BTT_TRIM.Size = New System.Drawing.Size(45, 22) + Me.BTT_TRIM.TabIndex = 2 + Me.BTT_TRIM.Text = "Trim" + TT_MAIN.SetToolTip(Me.BTT_TRIM, "Trim the file by setting trimming options and/or selecting chapters") + Me.BTT_TRIM.UseVisualStyleBackColor = True + ' 'TP_FPS_BITRATE ' - TP_FPS_BITRATE.ColumnCount = 2 + TP_FPS_BITRATE.ColumnCount = 3 TP_FPS_BITRATE.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) TP_FPS_BITRATE.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_FPS_BITRATE.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 51.0!)) TP_FPS_BITRATE.Controls.Add(Me.TXT_FPS, 0, 0) TP_FPS_BITRATE.Controls.Add(Me.TXT_AUDIO_BITRATE, 1, 0) + TP_FPS_BITRATE.Controls.Add(Me.BTT_TRIM, 2, 0) TP_FPS_BITRATE.Dock = System.Windows.Forms.DockStyle.Fill TP_FPS_BITRATE.Location = New System.Drawing.Point(6, 93) TP_FPS_BITRATE.Margin = New System.Windows.Forms.Padding(6, 0, 6, 0) TP_FPS_BITRATE.Name = "TP_FPS_BITRATE" TP_FPS_BITRATE.RowCount = 1 TP_FPS_BITRATE.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) - TP_FPS_BITRATE.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) TP_FPS_BITRATE.Size = New System.Drawing.Size(589, 28) TP_FPS_BITRATE.TabIndex = 6 ' @@ -577,7 +590,7 @@ Namespace API.YouTube.Controls Me.TXT_FPS.Location = New System.Drawing.Point(3, 2) Me.TXT_FPS.Margin = New System.Windows.Forms.Padding(3, 2, 3, 3) Me.TXT_FPS.Name = "TXT_FPS" - Me.TXT_FPS.Size = New System.Drawing.Size(288, 22) + Me.TXT_FPS.Size = New System.Drawing.Size(263, 22) Me.TXT_FPS.TabIndex = 0 Me.TXT_FPS.TextBoxWidthMinimal = 30 ' @@ -593,9 +606,9 @@ Namespace API.YouTube.Controls " to change." Me.TXT_AUDIO_BITRATE.CaptionWidth = 75.0R Me.TXT_AUDIO_BITRATE.Dock = System.Windows.Forms.DockStyle.Fill - Me.TXT_AUDIO_BITRATE.Location = New System.Drawing.Point(297, 3) + Me.TXT_AUDIO_BITRATE.Location = New System.Drawing.Point(272, 3) Me.TXT_AUDIO_BITRATE.Name = "TXT_AUDIO_BITRATE" - Me.TXT_AUDIO_BITRATE.Size = New System.Drawing.Size(289, 22) + Me.TXT_AUDIO_BITRATE.Size = New System.Drawing.Size(263, 22) Me.TXT_AUDIO_BITRATE.TabIndex = 1 ' 'TP_HEADER_BASE @@ -920,5 +933,6 @@ Namespace API.YouTube.Controls Private WithEvents CMB_PLS As PersonalUtilities.Forms.Controls.ComboBoxExtended Private WithEvents BTT_PLS_BROWSE As SCrawler.API.YouTube.Controls.ButtonRC Private WithEvents TXT_AUDIO_BITRATE As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents BTT_TRIM As Button End Class End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/VideoOptionsForm.resx b/SCrawler.YouTube/Controls/VideoOptionsForm.resx index 31b25a8..1d4889b 100644 --- a/SCrawler.YouTube/Controls/VideoOptionsForm.resx +++ b/SCrawler.YouTube/Controls/VideoOptionsForm.resx @@ -139,98 +139,96 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAFFJREFUOE9joAr49u3bf1Lw169f50O1QgBI0MnJCY4/vP8Ix8hiILqtrQ3TEFIM - AGGYIVDtpBsAwkQbgIyR1dDWAGLwqAGD0gByMFQ7JYCBAQChNviRiQ8ETwAAAABJRU5ErkJggg== + vAAADrwBlbxySQAAAEhJREFUOE9jYKAG+Pbt239S8NevX+djGODk5ATHH95/hGNkMRDd1taGaQgpBiAb + QrYBIEy0AdgMo70BxOBRAwalAeRguAGUAAChNviRcuLCdwAAAABJRU5ErkJggg== - iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAABGdBTUEAALGPC/xhBQAAE65JREFUeF7t - 3X2sJWddB/DdLi2lQG2hdOHuvfM887J7Cxca4ELTQMDWKigIFpBAEAgi9g+CJpJo9Q8NJhgBiYZIYspL - GlAKCkhEC4KgQlsLQkqhKi/lrYWWlxaw3dLddrerz/Q89+7dc2fbfTn3npf5fJJv2rS758z85nnOzJz5 - nZktAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAABGdBTUEAALGPC/xhBQAAE0xJREFUeF7t + 3X2MZWddB/BZSkspUFsoXZidOc/vnOfsTmGgARaaBgK2VkFBsIAEgkAQsX8QNJFEq39oMMEoSDREElOB + EFAKCkhEAUGqQlsLQkoBlZfy1kJ5awH7QrvtbldzdndmZ597Snd37szce8/nk3xTQjsz5/zOefbO3vO9 + 58zNAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMK3O3r79wVUIz65jfGNVxI/VIX69CvGO9M//a9P+e8o3B/8v - vKn9s+3fyX8dAJgmaWd+fl3E96Wd/E9XdvZHkfbvXNa+Rn45AGCS3bvjj/E/h3box5OrmxjPyy8PAEyS - XXO7zqhCeH/HDnwUOdCE+J6zdux4eH47YIrEGE8uy/Ls9Bnx/LooL0oH9b9Th/I1TVG+rCqKC+q6Xsh/ - FJgmO8vy6WknfdPQTnsjckMdwlPy2wITLO3wF6si/lGas1ekuXvX0Fzuyg9S3psOCl6qDwimQB3ji9Ok - 3btmEm907kpnEa/Mbw9Mlq1pB/6cdHZ/ZcfcPZrcXoXyrVVVFfl1gUmSdsS/libqPUMTd5NSvjktwrbB - kgDjVi1UT26K+Nnu+XrMuaud60uPWHpIfhtg3JqyfEaanHcPTdZNTRPCPy4uLj40LxIwBudt2fKAtOP/ - 0zQnN+5koIg3tpca81sC49J+LZcm5a3rJulYEq6LSV40YBOFEB6V5uFV6+flRiTsSwf9r81vDYzBCSO4 - vjfq/KAuiqfm5QM2QRPjuWnubUbz71DCn6W33zpYCmDT1EX5m92Tcuy5q47xFXkxgQ3UduqnOXfn0Bzc - xJSvz4sCbIb2pzlp8v1w/WScnKSzkjekRT1hsMTAKC0vL5/Ydud3zb1NT1FelBcL2GiDm3d0TMTJy0ea - pjk1LzYwAu3NvtLc+uTQXBtn7tYYCJtja/vQno5JOJFpQrzWb4hhNJoQnpjm1Q3D82wCcnNRFKfnxQQ2 - Qttk1zH5JjzhFmcIcHzyzb6O5aFem5J0sP/OvKjARmg7b7sm3xRkT3vDorwawJHb1t6Ep2NOTVoOtDch - yssMjFr6IPh8x8SbnsT4lrQamgPhCMzPzz+sifHjnXNpMnN5XnRglJaWlk5KE2z/0ISbxnzQQ0bgvlXz - 1ePSXPnG0NyZ+DRF8Zi8CsCo7Azh0V0TbkrzRc2B0G3wIJ9429CcmZLce4MgYJTyff87JtzU5uayLM/J - qwcM7vD5+jQ3DgzNlWnKDXldgFFJZwW/2jHZpj1727uZ5VWE3mofqJXmw4eG5sdUpqqqXXm1gFGoQnhJ - 12SbgRxoYvzjtIruK04vxRjPSvPgK0PzYmqTPqtemVcNGIU6xgu7JtusJH1ovH9ubu6UvLrQC2ncPyuN - /58Mz4fpTvnmvHrAKJQL5dO6J9ssJXxucWFhLq8yzLKtaUf5h2ncb9zz+8eUKsYP53UERmHX/PyOrsk2 - g7nJDUWYZUuPWHpIE8oPdIz92UiMn86rCoxIOmOYta8KD5uftk2Peb1hZtTzdVOHcF3HmJ+ZVCF+Ia8u - MCppcl0+PNlmOG1zYPtYYc2BzIQ0np+ZxvWPh8b5LObqvMrAqEzRo4BHmctijCfnEsBUqkP5u2ksz8Kd - PI8g5SfyagOj0jbIpQk2c01DR5Brmh3NfC4DTI324LWO8V0dY3pm48mAsEGm7OEgo0sRb9wZ4+NzGWDi - lWUZ0ti9Zt1YnvUU8fdyCYBRmsFbAh9xqhDvqEN4Xi4FTKz8s93vD4/hPiSdpJyXywCMWPtrgKuGJ12P - ck/6gPmDXAuYOHVR/lY6UN3XMXb7kDv17MAGqhaqJ6WJ1sdegDUJ726a5oG5JDB2917vL+Kl3eO1N/lQ - LgewUdIO8E0dk69vubosy+25JDA2bYNuFeJnOsZovxLjhbkkwEZZXl4+0QfOvfl2Ogg4O5cFNl1dFE9N - 4/B7Q+Oyj7mh/VzKZQE2UtM0j6iL+LWOidizhN3OPBiHuigvSmPwrvVjsn9pQnh1LguwGQa3Fo3fHp6M - Pcw97c1WcllgQy0tLZ2UDr7/qmMc9jJNiF/WkwNjMHhQ0GzfX/yIU8RLfRCxkdq+kzTfrugcf/3MgZ1l - +fRcHmCztU8Yq2P8h47J2cdcpTmQjdCE8IQ0vnzjdkjKP8nlAcZoWxXin3dP0n4l1eGb9UL92FwXOG51 - Ub48ja09w2Otz2nvTJpKs21QIWDs0lnKb6TJqTEphN3NQvncXBY4VtvSju4N3WOs17l6cXHxoblGwKRo - r8mlHeAtHZO2b9mfDohem8sCR2XX3K4z0hj65NCYklSTGONpuUzApNlVFFWaqP81NHF7mvD2tnM7lwbu - V/vwqTR2vrV+LPU7VSjf4ff+MAU0B65NeWVd12fm0sBhpTnzosHDp7rGUV8T9lVFvDiXCJgSrmEezDea - onhMrgsM25rmyuvSODkwNG56nvZyYvi5XCNg2mgOXM3tVVH9ci4L3KtpmlN9W7Y+VYhfiEkuEzCt8n3L - fzA8yXuY/b7OZEVZlovt3ew6xknf8965ublTcpmAaac5cG3C2zQ09Vv7bVAaC/+7fmz0Og6QYVZpDlyT - GD/dPlgpl4b+2Nru5NIYuGfdmOhxmhB/VBblL+QaATNKc+DBfH1nCI/OdWHGtTewSdv874fGgIT4xfYb - wlwmYNZpDlzNbVUIz85lYUblJ2i6BDacGP/u7O3bH5zLBPSF5sDV7K+L+Nu5LMyYtJP7xbSNfzy0zfue - A+03gak8WwdVAnpHc+CaxHiJ5sCZsnK9f/+6bd3v3JZ2/r+SawT0mebAg0kfjB93v/Pp136t3X693bWN - e56v6nsBhmkOXE24Ph0EnJXrwpSp63qhDuXnu7dtn1P+U1VVP5PLBHAozYGDtD+LchvU6TN4Iqa+lqGs - XO8/YVAlgMPQHLiSsC+dNb0ml4UJVxflRWm73b1+O/Y5YXcVwvNziQDun+bANYnxkvO2bHlALg0TJsZ4 - cl3ESzu3Xa8Trm+KYimXCeDIaQ48mKqIH9McOHl2zc/vaIr42a5t1vN8tCiK03OZAI6J5sCVFPFr7QNk - cl0Ys3yp6nvrtlO/s3K9f9ugSgDHSXPgILk58PxcFsYkX+93J8s1qUK8oynKF+YSAYyO5sCVhH3pgOjV - uSxsoqZpHpjq//bu7dLjFPHGND+Xc5kARk9z4JrE+JZUEl+1bpLFhYW5VPf/WLcd5N/ruj4zlwlg42gO - PCQfdXOVjdeE8MRU6xuGai9uXw2MgebA1YTrFkMoc10YsaYoX5rqfOf6uvc6e9LO/xW5RACbT3Pgam5N - B0Q/m8vCCLT3XnCQ2ZXwnWqhenIuE8D4aA5czV3OykZj19yuM1I9PzlUXwnhirIst+cyAYyf5sA1GTQH - uu/6MdoZ4+NTHb+1rq59j+v9wKTSHHhIPtI0zam5NByhNH5enGr306Fa9j1720ttuUQAE0tz4Epi/FJM - cl24b8ZNd25KdTk31whg8mkOXEm4pX1EbS4LHebn5x+WdnIf765fr3NVCOFRuUwA00Nz4Gr21kX58lwW - 1qjmq8el+nxjqF4S4yVLS0sn5TIBTB/NgWuiOfAQTVE+J9XltnV16nXCvqqIF+cSAUw3zYGH5INnb9/+ - 4Fyavtra7uRSLe4Zqk3f88MmxvNyjQBmhiavg/liVVVFrkuvLC4uPjSt/4eG6iEhXlOWZchlApg9mgNX - c3P6wD8nl6UXqvlqZ1rv/xmqQ+/ThPJv5ufnH5TLBDC7NAeuZm97n/tclplWhfCstL4/GVr/nsf1fqCH - NAeu5kB7aSSVZFabA13v786tVVFckGsE0C+aAw8mnSG/f25u7pRcmpnQbt8mlB/oWt8+pwnxWk+PBNAc - uJoqxC/MSnNgs7BQ1yFc17WePc97Z+1AD+C4aA5czU3T/qjXdED3zLQePx5ar75nf77ev3VQJQBWaQ5c - zZ4qhJfkskyVuigvapvbOtapt2lC/FFTls/IJQKgi+bA1aw0B07FGWOM8eQ6xnd1rEe/E+OX2jGdywTA - fdEcuCYx/u2kXzNudjTz6az/c53L3+NUMX7YI6EBjp7mwIO5pt3J5rpMlHKhfFpavu8PLW/fM+s/7QTY - eJoDV/PduiiWc1kmwuB6f7x7aDn7ntvrGC/MJQLgeGgOHKQK8Y46hOflsoxN0zQPbEJ8Z9cy9jxf3RnC - o3OZABgFzYGrab9efl0qyViaAxcXFubSgchnOpar77k8xnhaLhMAo6Q5cE2K+L7NfoBMHcJT0nvfvG5Z - +h3X+wE2iebAg7m6LMvtuS4bKl/v14txSMLuKoQX5BIBsBk0B64kfCfV4om5LCN33pYtD3DA1ZVwfb1Q - PzaXCYDNpDlwJWH3RnSe75rbdUZ6/X9b/369z0eLojg9lwmAcdAcuJqV5sCRaEJ4QnrNbw+9R9+zcr1/ - 26BKAIyV5sBDcll7W95cmmPSPocgvc6dQ6/b9+xpivJluUQATBDNgQdz1TE2B6phV4p446TdhAmAIens - 9ZXpQ1tz4KA58Am5LPfrrB07Hp7+zr90v1Z/UxXxU3Vdn5nLBMAk0xy4krC7WSifm8tyWGVZnl2F+M3u - 1+hxYrxkeXn5xFwmAKaB5sDV7E9nsRfnsqyTdnIvGtxiuPPv9jV7Ul1ekUsEwLTRHHgwVSjfsbS0dFIu - TWtr+6uB9P8ODP/Znue7ZVmek2sEwBTT2Laa8sr2enb7jHoHRl0JV8QYH5nHDQCzwJ0DV/P1tKO7vuO/ - 9zpVKN/qej/AjNIcKB3Z24TyVXmIADCrNAfKmtzUxHhuHhoAzDrNgZJyVQjhUXlIANAjmgN7m/Du471d - MgBTzp0D+5Sw777uiQBAz2gO7EPCLSnn500OAAOaA2c615RlGfKmBoBDaQ6cvTQhvmd+fv5BeRMDwGFp - DpyJuN4PwDHQHDjVubUqigvypgSAo6M5cPrShHjtYghl3oQAcGw0B05Rivi+ubm5U/KmA4Djozlw4rM/ - X+/fOthiADA6mgMnME2IP2rK8hl5GwHAxtAcOFH5SozxrLxpAGBjaQ4cf6oYP9w0zal5kwDA5tAcOLYc - aC/FpE1wwmBLAMAm0xy46bk91fvCXH4AGCvNgZuRIn6tKYrH5JoDwGTQHLihuTzGeFouNQBMFs2BI4/r - /QBMB82Bo0rYXYXwglxWAJh8mgOPN+H6eqF+bC4nAEwVzYHHkiL+c1EUp+caAsB00hx4FInxLalk2waV - A4AppznwfrOnLsqX53IBwOzQHHiYFPHGaqF6Ui4TAMwezYGHpirip+q6PjOXBwBmmubANjFesry8fGKu - CQD0Q4+bA/dWMf56LgMA9E8PmwO/W5blOXn1AaC/+tMcWF4ZY3xkXm0AYOabA2O8ZGlp6aS8ugDAGrPY - HLi3CeWr8voBAIczQ82BN6UDmnPzagEA92f6mwPLz1dVVeTVAQCO1LQ2B1Yh/PX8/PyD8moAAEdrupoD - w76qiBfnRQcAjtMUNAeGW1LOz8sLAIzKBDcHXlOWZciLCQCM2gQ2B142Nzd3Sl48AGCjTEhz4H7X+wFg - k425OfDWqqh+Pi8KALDJtqWDgDemHfKBoR30hqUJ8dqY5PcHAMalKcrnpJ3z94Z31qNO+/t+1/sBYIKk - k/LT6hD+Mu2oR/4rgXTW/+X02r+U3woAmDTtz/GaIv5F2nH/ZHhHfpS5J+Vf01n/S9LLbhu8OgAw0dpb - 8TYL5XPTmfvb0o78v/MOvWtHvybtzXzKT1Qx/n5d1wv5pQCAaXXvAUFRLLXd+3WMFzZF+cKUl7X/rIri - gsWFhbn8RwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJQ6e/v2BzQpPSNHvLap4sM5xVeaFLflFP/XpfvfTYqvHfx3 + 6XXdf9t9Tfl9AIApkFM6P1fxrpzixysv9seQ7msu7b5H+X0BgAl04IU/4j97XtSPN1e1EeeVPwcAmAC7 + 5ned0aT07p4X8HFkf5viHWft2PGQ8ucCky8iTq7r+uwmpefkqr6ojfitnOpXtlX94qaqLsg5L5ZfA0yB + nXX9lJzihp4X7nHnupzSE8ufD0yeuq6Xmir+IKd0eU5xZ896LvO9nOKdbVW/SA8IpkCOeEFOsadnMW9U + 7mxSelm5HcBE2NZW9TNzqq/oWbvHkluaVL+xaZqq/AHABGhS+pWc4u6exbsJqV8/Nzd3QrlNwNZoFpsn + tFV8cnStrit3dmt9+aHLDyx/HrBF2rp+ak5xV8+C3bS0Kf3j0tLSg8ptAzbPeXNz922r+OMN/ctAFdd3 + lxrLnw1ssu5tuZzippFFuiVJn4+IKLcR2HgppYfnFFeOrsuNSNrbpvSqchuAzXOfMVzfG3e+l6vqSeWG + AhunjTh3k8q/RdKfdl2DcnuADZar+tdHF+RE5M4c8dJye4Hx65r6OcXtPetwk1K/ptwmYAN1H83JKb4/ + uhgnJ23En3TvUpTbDqzf7t27T+za+eW625JU9UXl9gEb5ODNO3oW4uTlg23bnlpuP3D8upt95RSX9ay3 + rcpdioGwObZ1D+3pWYQTmTbFNT5DDOPRpvS4gzfiGl1rW5xvV1V1erm9wBh1JbuexTfhSTf6GwKsz6Gb + fR3PQ702JW2Kt5TbDIxR17wtF96U5I7uhkXl/gD36oTuJjw9a2rSsr+7CVG58cCY5FR/umfhTU8i3qAc + CEdnYWHhwW3ER0bW0eTmA+U+AGOwvLx8Uk6xr2fRTVve6yEj8JM1C82jc4qv9qyfiU5bVY8s9wVYp50p + PaJcbFOczyoHQr+DD/KJm3vWzRTkwA2CgHE6dN//ngU3tfl2XdfnlPsJA9bd4fM13fX0nvUyLbmu3Clg + ndqq/uWexTbt2dPdzazcVxia7oFaOcX7etbI1KVpml3l/gHr0KT0wnKhzUj2txF/6L7iDFVEnJVTfLFn + bUxlmpReVu4jsA454sJyoc1SmpTePT8/f0q53zDLmpSenlP8qFwP05369eV+AutQL9ZPHl1os5b0qaXF + xfly32EGbcup/v2c4u7RdTDdaSLeX+4ssA67FhZ2lAttRnODG4owy5YfuvzANtXv6Tn3ZyMRHy/3GVif + bbP3VuE95sdd6bEcAEy7vJDbnNLne875mUmT4jPlfgPr1N1pq1xsM5yuHNg9Vlg5kJnQRjwtp/hhz7k+ + a7mq3HdgnaboUcDjzKURcXI5C5gmOdW/PSN38jyK1B8t9x9Yp64gN4uloaPI1e2OdqGcB0y67pfXHPG2 + nnN6ZuPJgLBBpuzhIONLFdfvjHhMOQ+YVHVdp+6X15FzedZTxe+UswDGYAZvCXzUaVLcllN6djkTmDSH + Prb73fIcHkLaiPPKeQDj0X0a4Mpy0Q0od7cRv1cOBSZFrurfyCnt7Tl3h5DbdXZgAzWLzeMH2gVYk/T2 + tm3vV84GtsqB6/1VvHX0XB1U3lfOBRiznNLrehbf0HJVXdfby9nAZusKuk2KT/Sco8NKxIXlbIAx2717 + 94n+wDmQb9R1fXY5H9gsuaqelFN8p+fcHFqu6/5cKucDbIC2bR+aq/hyz0IcWNKt/ubBVshVfVFOcefo + OTm8tCm9opwPsIEO3lo0vlEuxgHm7u5mK+V8YCMsLy+flKv4y57zcJBpU3xBJwe2wMEHBc32/cWPOlW8 + 1R9EbKSud5JTunzk3Btu9u+s66eUcwI2SfeEsRzxDz2Lc4i5UjmQjdCm9FjvuJWp/6icE7D5TmhS/Nno + Ah1emhRfy4v5UeWA4Hjlqn5JTnFHea4NOd2dSbs/d8pZAVukTenXFJO6pFvbxfpZ5XzgGJ3QPZly9Pwa + fK5aWlp6UDksYIt11+RySjf2LNqhZV+b0qvK+cDR2DW/64yc4rKe82rouSwiTivnBUyIXVXV5BT/1bN4 + B5j0pq65Xc4I7kn38Kmc4uuj59Kw06T6zT7vD1NAOXBt6ityzmeWM4JSjnj+wYdPlefQkJP2NlVcXM4K + mGyuYR7OV9uqemQ5IDhkWxvx6u6jbT3nzoDTXU5MP1MOC5gSyoGruaWpml8s58OwtW17qnfLRtOk+ExE + RDkvYMocum/598pFPsDs83YmK+q6XuruZtdzngw975yfnz+lnBcwpZQD1yb9lULTsHXvBuUU/zt6bgw6 + fkGGWaUcuCYRH+8erFTOiJm3rXuRO/gciZ7zYqBpU/ygruqfK4cFzBblwMP5ys6UHlEOiNnU3cAmp/j7 + nvNg6Pls9w5hOS9gRikHrubmJqVnlPNhthx6gqZLYGUi/u7s7dsfUM4LmHHKgavZl6v4zXI+zIYc8fM5 + xQ97jvuQs797J7C7JFLOCxgI5cA1ibhEOXCmrFzv3zdyrIedm9uIXyqHBQyQcuDhdE86c7/z6de9rd29 + vV0eX4kv6b0AJeXA1aRrI+KsckBMh5zzYk71p0eP69BT/1PTND9VzgvgAOXAg+k+FuU2qNPn4BMx9VqK + rFzvv085L4AjKAeuJO3NqX5lOR8mU67qi3KKu0aP45CTbm1Sek45K4B7pBy4JhGXnDc3d99yRkyGiDg5 + V/HWkeM2+KRr26paLucFcK+UAw+nqeLDyoGTZ9fCwo62ik+Wx0viQ1VVnV7OC+BYKAeupIovdw+QKQfE + 1jh0qeo7I8dp2Fm53n9COS+A46IceDCHyoHnl/Nhcx263j/483FtmhS3tVX9vHJWAOumHLiStLdN6RXl + fNh4bdveL6f0ptFjMvBUcX2uqt3lvADGRjlwTSLe4K3WzbO0uDifU/zHyHGQf885n1nOC2DslAOPyIfc + XGXjtSk9Lqe4rmf+w47bVwNbQDlwNenzSynV5YAYj7aqX5RT3D4690Hnjhzx0nJWAJtGOXA1N7URP13O + h+PX3XvBL5l9Sd9sFpsnlPMC2HTKgau509/KxmPX/K4zcorLemY88KTL67reXs4LYMsoB67JwXKg+64f + p50Rj8kpvj4y16HH9X5gUikHHpEPtm17ajkjfrIc8YKc4sc98xxy9nSX2spZAUwa5cCVRHwuIqIcEL2c + N/25oY04txwWwMRSDlxJurF7RG05Hw5bWFh4cBvxkdHZDT5XppQeXs4LYOIpB65mT67ql5TzYW6uWWge + nVN8tWdmw07EJcvLyyeV8wKYGsqBa6IceIS2qp+ZU9w8MqdBJ+1tqri4nBXAVFIOPCLvPXv79geUMxqY + bd2LXE5xd898hpzvtxHnlcMCmHZKXofz2aZpqnJAQ7C0tPSgnOJ9PTMZeq6u6zqV8wKYGcqBq/l2Xdfn + lPOZZc1CszOn+J+eWQw6bar/ZmFh4f7lvABmjnLgavZ097kv5zOLmpSenlP8qGcGA47r/cAAKQeuZn93 + aWSGy4Gu9/fnpqaqLiiHBTAIyoGH06T07vn5+VPKGU2z7vi2qX5Pua9DT5viGk+PBFAOXE2T4jOzUg5s + Fxdz95jkch8l3jlrv+gBrIty4GpumPZHvbYRT8spftizb0POvkPX+7eV8wIYPOXA1dzRpPTCcj7TIFf1 + RV25rWefBps2xQ/aun5qOSsA1lAOXM1KOXAq/sYYESfniLf17MewE/G57pwu5wVAD+XANYn420m/Ztzu + aBdySp8a2faBp4l4v0dCAxw75cDDubp7kS0HNAnqxfrJOcV3e7Z5yJn1j3YCbDzlwNV8K1fV7nI+W+ng + 9f64q2dbh5xbcsSF5awAOA7KgQfTpLgtp/Tscj6brW3b+7Up3lJun8SXdqb0iHJeAKyDcuBqureXX71V + 5cClxcX5JsUnerZr6PlARJxWzguAMVAOXJMq3rXZD5DJKT2xe4jRyLYMO673A2wS5cDDuaqu6+3lgDbC + oev9uhhHJN3apPTcclYAbCDlwJWkb7YpPa6cz7icNzd3X79w9SVdmxfzo8p5AbAJlANXkm7diOb5rvld + Z+QU/zb68wafD1VVdXo5LwA2kXLgalbKgWPRpvTYnOIbPT9nyFm53n9COS8AtoBy4BG5tLstbzmjY9E9 + hyCnuL3new85d7RV/eJyVgBsPeXAw7nyOMuBZtiXKq6ftJswAVBoUnqZcmCXA+XAx5bzuSdn7djxkJzS + v4x+n2GnqeJjOeczy3kBMIGUA1eSbm0X62eV8ynVdX12k+Jro18/8ERcsnv37hPLeQEwwZQDV7OvqeLi + cj4rcsTzD95ieOTrhpw7csRLy1kBMCWUAw+nSfWbl5eXT1oznm3dpwa6Znv53w4836rr+pw1cwJgSim2 + raa+orue3T2j3i9GfUmXR8TDyhMIgCnmzoGr+cqBu9iN/v+DTpPqN7reDzCjlAOlJ3vaVL+8PFcAmDHK + gbImN7QR55bnCAAzSjlQupslpZQeXp4bAMw+5cDBJr19vbdLBmDKuXPgkJL2/qR7IgAwMMqBQ0i6Mad0 + fnnsARg45cCZztV1XafymAPAAcqBs5c2xTsWFhbuXx5rACgpB85EXO8H4DgoB051bmqq6oLymALAUVEO + nL60Ka5ZSqkujyUAHBPlwClKFe+an58/pTyGAHBclAMnPvsOXe/fVh47AFgv5cAJTJviB21dP7U8WAAw + VsqBE5UvRsRZ5TECgA2hHLj1aSLe37btqeWxAYANpRy4ZdnfXYqZm5u7T3lMAGBTKAduem7JEReWxwEA + toJy4Gakii+3VfXIcvgAsKWUAzc0H4iI08qZA8BEUA4ce1zvB2A6KAeOK+nWJqXnlvMFgImlHLjepGvz + Yn5UOVcAmAbKgceTKv65qqrTy2ECwFRRDjyGRLyh+8WpnCEATCXlwHvNHbmqX1LODQCmnnLgPaSK65vF + 5vHlvABgZigHHpmmio/lnM8s5wQAs0g5sEvEJbt37z6xHA4AzLQBlwP3NBG/Ws4DAAZjgOXAb9V1fU45 + BwAYnOGUA+srIuJh5f4DwGDNfDkw4pLl5eWTyv0GAGazHLinTfXLyx0FAAozVA68oY04t9w/AOAeTH85 + sP500zRVuV8AwL2Y1nJgk9JfLyws3L/cHwDgKE1XOTDtbaq4uNwHAOD4TEE5MN2YUzq/3HAAYJ0muBx4 + dV3XqdxeAGBMJrAceOn8/Pwp5XYCAGM2IeXAfa73A8Am2+Jy4E1N1fxsuU0AwOY4IUe8NqfY3/MivSFp + U1wTEVFuCACwydqqfmZO8Z3yxXrc6T7f73o/AEyQiDgtp/QXG/EpgTbFF3JKv1D+TABgQnQfx2ur+POc + 4kflC/kx5u6c4l+blF7YXWoofw4AMIG6W/G2i/Wzckp/lVP896EX9PJFvkh3M5/6o03E7+acF8vvCQBM + mQO/EFTVctfezxEXtlX9vLaqX9z9s6mqC5YWF+fLrwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6LEtW/4flgYiLD1qeX0A - AAAASUVORK5CYII= + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAYIj+H5YGIizEb/aEAAAAAElFTkSuQmCC @@ -248,115 +246,113 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAFFJREFUOE9joAr49u3bf1Lw169f50O1QgBI0MnJCY4/vP8Ix8hiILqtrQ3TEFIM - AGGYIVDtpBsAwkQbgIyR1dDWAGLwqAGD0gByMFQ7JYCBAQChNviRiQ8ETwAAAABJRU5ErkJggg== + vAAADrwBlbxySQAAAEhJREFUOE9jYKAG+Pbt239S8NevX+djGODk5ATHH95/hGNkMRDd1taGaQgpBiAb + QrYBIEy0AdgMo70BxOBRAwalAeRguAGUAAChNviRcuLCdwAAAABJRU5ErkJggg== iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6 - JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAeElE - QVQ4T2P4//8/RRhMFHQfKgDi/yAaXQEhDCZAmkNbnvyXta4CciESLEws//FhmDqYAQUgzUBMngsowVgF - ScFgYjQQsUsQi8FEYsXyAiD+D6LRFRDCYAKk2bPo6H9J40wgFyKBLeCQMUwdzIACkGYgHnKB+J8BAD5Q - tqhi4tzWAAAAAElFTkSuQmCC + JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAACXBIWXMAAAsMAAALDAE/QCLIAAAAd0lE + QVQ4T2P4//8/AyUYTBR0Hyoo6D70H0SjKyCEYQb8D2158l/Wuuo/TIKFieU/PoxuQAFIs6x1FXkuoARj + CJCKwcRoIGIKkoLBRGLF8oLEiuX/QTS6AkIYZsB/z6Kj/yWNM8kLRJDNIM2SxpnkuYASjCFAKgYAPlC2 + qCS4LQgAAAAASUVORK5CYII= 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 - AAAASUVORK5CYII= + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAYIj+H5YGIizEb/aEAAAAAElFTkSuQmCC @@ -383,122 +379,122 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go - tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX - AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + vAAADrwBlbxySQAAAHpJREFUOE+1kVEKgDAMQ3e2/e+MXqpn6W/HxM7SpkIVB4HxSMKiTUTaFwVQ1X25 + DjMfSxskHBYsAxHJkjUjHgrUNMY4peaMPxb03rcZMVhgn2oDKAwn+L0aROH/Cny4NAGFSx8x+10ZDwV2 + gt+LOCxQsw1nPBS8VQBVTTzyhrdZSUm7AAAAAElFTkSuQmCC iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go - tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX - AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + vAAADrwBlbxySQAAAHpJREFUOE+1kVEKgDAMQ3e2/e+MXqpn6W/HxM7SpkIVB4HxSMKiTUTaFwVQ1X25 + DjMfSxskHBYsAxHJkjUjHgrUNMY4peaMPxb03rcZMVhgn2oDKAwn+L0aROH/Cny4NAGFSx8x+10ZDwV2 + gt+LOCxQsw1nPBS8VQBVTTzyhrdZSUm7AAAAAElFTkSuQmCC iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - wwAADsMBx2+oZAAAAR5JREFUOE+VkjFqwzAUhn2D9iShRyi+QhYbGujg3ZATZPKYdC6FQhPwlAMkg3dP - WQwhyWIyJIUW5NqyPb7oCVtIlhVTwYf8nv7/t2zJagel9KmqKsIACYL9RjI8UHz5zshougZr/AEvbxEP - aZCDBY3VslixaJvX3wzkkDiOwbZtDRGA5vdNAg+TL27qgmt5XkBG/gTdAG7Gt+3PP9oOaEGFCVEC6rp+ - 5g9MfM/c5e4OsEZMZkQEtGL5H2DdZ5JRArDwPA+iKII0TfkC9vroC9j5vq8JTWw3WzWgLMtZGIaa0MR8 - vlAD8PYlSaIJTTiOowY0p0Bc19XEJo6HE59FAPuMzyAINKGJ1XLFZxHALtMrnkBXOIQIIIQ8YvF/KrgB - cMaRN0UdBBkAAAAASUVORK5CYII= + vAAADrwBlbxySQAAARlJREFUOE+VkjFqwzAUhn2D9iShRyi+QhYbWujg3dATZPKYZC6BQhvw1AMkg3dP + XQyl7WIyJIEW5CbS0/jKE5GwpCghgg9s6/8/y5Kj6DA45zcAwAAAezB6rjNnB4XX244NHt8wGs7wblop + yRGxwZQBYKIfbn477EvqusY4jj2MgMpPiwav7l9UyYXmdrs9duzP4ApUmd72sfrxVsD33JQISyClvFUX + w9nJssvJFei9CJUtgQ7394Du3YKLJaCbLMuwqips21ZNuDve/35X8J7nuRcMsVwsbYEQYlSWpRcMMR5P + bAH9fU3TeMEQSZLYgsMpsDRNvXCIr89vWyCEeC6KwguGmL/ObYGU8oFOwA2ewwgYY9f6f7iUf3DGkTcu + khP7AAAAAElFTkSuQmCC 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 - cMaRN0UdBBkAAAAASUVORK5CYII= + vAAADrwBlbxySQAAARlJREFUOE+VkjFqwzAUhn2D9iShRyi+QhYbWujg3dATZPKYZC6BQhvw1AMkg3dP + XQyl7WIyJIEW5CbS0/jKE5GwpCghgg9s6/8/y5Kj6DA45zcAwAAAezB6rjNnB4XX244NHt8wGs7wblop + yRGxwZQBYKIfbn477EvqusY4jj2MgMpPiwav7l9UyYXmdrs9duzP4ApUmd72sfrxVsD33JQISyClvFUX + w9nJssvJFei9CJUtgQ7394Du3YKLJaCbLMuwqips21ZNuDve/35X8J7nuRcMsVwsbYEQYlSWpRcMMR5P + bAH9fU3TeMEQSZLYgsMpsDRNvXCIr89vWyCEeC6KwguGmL/ObYGU8oFOwA2ewwgYY9f6f7iUf3DGkTcu + khP7AAAAAElFTkSuQmCC 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 - cMaRN0UdBBkAAAAASUVORK5CYII= + vAAADrwBlbxySQAAARlJREFUOE+VkjFqwzAUhn2D9iShRyi+QhYbWujg3dATZPKYZC6BQhvw1AMkg3dP + XQyl7WIyJIEW5CbS0/jKE5GwpCghgg9s6/8/y5Kj6DA45zcAwAAAezB6rjNnB4XX244NHt8wGs7wblop + yRGxwZQBYKIfbn477EvqusY4jj2MgMpPiwav7l9UyYXmdrs9duzP4ApUmd72sfrxVsD33JQISyClvFUX + w9nJssvJFei9CJUtgQ7394Du3YKLJaCbLMuwqips21ZNuDve/35X8J7nuRcMsVwsbYEQYlSWpRcMMR5P + bAH9fU3TeMEQSZLYgsMpsDRNvXCIr89vWyCEeC6KwguGmL/ObYGU8oFOwA2ewwgYY9f6f7iUf3DGkTcu + khP7AAAAAElFTkSuQmCC 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 \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/VideoOptionsForm.vb b/SCrawler.YouTube/Controls/VideoOptionsForm.vb index 5155791..e29485c 100644 --- a/SCrawler.YouTube/Controls/VideoOptionsForm.vb +++ b/SCrawler.YouTube/Controls/VideoOptionsForm.vb @@ -465,6 +465,9 @@ Namespace API.YouTube.Controls TXT_FILE.Text = f End If End Sub + Private Sub BTT_TRIM_Click(sender As Object, e As EventArgs) Handles BTT_TRIM.Click + Using f As New VideoOptionsTrimForm(MyContainer) : f.ShowDialog() : End Using + End Sub Private Sub TXT_SUBS_ActionOnButtonClick(ByVal Sender As ActionButton, ByVal e As ActionButtonEventArgs) Handles TXT_SUBS.ActionOnButtonClick Select Case Sender.DefaultButton Case ADB.Open diff --git a/SCrawler.YouTube/Controls/VideoOptionsTrimForm.Designer.vb b/SCrawler.YouTube/Controls/VideoOptionsTrimForm.Designer.vb new file mode 100644 index 0000000..e25a4ed --- /dev/null +++ b/SCrawler.YouTube/Controls/VideoOptionsTrimForm.Designer.vb @@ -0,0 +1,166 @@ +' Copyright (C) Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.YouTube.Controls + + Partial Friend Class VideoOptionsTrimForm : Inherits System.Windows.Forms.Form + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Me.components = New System.ComponentModel.Container() + Dim CONTAINER_MAIN As System.Windows.Forms.ToolStripContainer + Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel + Dim TP_OPTIONS As System.Windows.Forms.TableLayoutPanel + Dim TT_MAIN As System.Windows.Forms.ToolTip + Me.LIST_TRIM = New System.Windows.Forms.ListBox() + Me.CH_DEL_ORIG = New System.Windows.Forms.CheckBox() + Me.CH_ADD_M3U8 = New System.Windows.Forms.CheckBox() + Me.CH_SEP_FOLDER = New System.Windows.Forms.CheckBox() + CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer() + TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + TP_OPTIONS = New System.Windows.Forms.TableLayoutPanel() + TT_MAIN = New System.Windows.Forms.ToolTip(Me.components) + CONTAINER_MAIN.ContentPanel.SuspendLayout() + CONTAINER_MAIN.SuspendLayout() + TP_MAIN.SuspendLayout() + TP_OPTIONS.SuspendLayout() + Me.SuspendLayout() + ' + 'CONTAINER_MAIN + ' + ' + 'CONTAINER_MAIN.ContentPanel + ' + CONTAINER_MAIN.ContentPanel.Controls.Add(TP_MAIN) + CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(434, 236) + CONTAINER_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + CONTAINER_MAIN.LeftToolStripPanelVisible = False + CONTAINER_MAIN.Location = New System.Drawing.Point(0, 0) + CONTAINER_MAIN.Name = "CONTAINER_MAIN" + CONTAINER_MAIN.RightToolStripPanelVisible = False + CONTAINER_MAIN.Size = New System.Drawing.Size(434, 261) + CONTAINER_MAIN.TabIndex = 0 + ' + 'TP_MAIN + ' + TP_MAIN.ColumnCount = 1 + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.Controls.Add(Me.LIST_TRIM, 0, 1) + TP_MAIN.Controls.Add(TP_OPTIONS, 0, 0) + TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + TP_MAIN.Location = New System.Drawing.Point(0, 0) + TP_MAIN.Name = "TP_MAIN" + TP_MAIN.RowCount = 2 + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.Size = New System.Drawing.Size(434, 236) + TP_MAIN.TabIndex = 0 + ' + 'LIST_TRIM + ' + Me.LIST_TRIM.Dock = System.Windows.Forms.DockStyle.Fill + Me.LIST_TRIM.FormattingEnabled = True + Me.LIST_TRIM.Location = New System.Drawing.Point(3, 33) + Me.LIST_TRIM.Name = "LIST_TRIM" + Me.LIST_TRIM.Size = New System.Drawing.Size(428, 200) + Me.LIST_TRIM.TabIndex = 0 + ' + 'TP_OPTIONS + ' + TP_OPTIONS.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] + TP_OPTIONS.ColumnCount = 3 + TP_OPTIONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!)) + TP_OPTIONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33334!)) + TP_OPTIONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33334!)) + TP_OPTIONS.Controls.Add(Me.CH_DEL_ORIG, 0, 0) + TP_OPTIONS.Controls.Add(Me.CH_ADD_M3U8, 1, 0) + TP_OPTIONS.Controls.Add(Me.CH_SEP_FOLDER, 2, 0) + TP_OPTIONS.Dock = System.Windows.Forms.DockStyle.Fill + TP_OPTIONS.Location = New System.Drawing.Point(3, 3) + TP_OPTIONS.Name = "TP_OPTIONS" + TP_OPTIONS.RowCount = 1 + TP_OPTIONS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_OPTIONS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 23.0!)) + TP_OPTIONS.Size = New System.Drawing.Size(428, 24) + TP_OPTIONS.TabIndex = 1 + ' + 'CH_DEL_ORIG + ' + Me.CH_DEL_ORIG.AutoSize = True + Me.CH_DEL_ORIG.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_DEL_ORIG.Location = New System.Drawing.Point(4, 4) + Me.CH_DEL_ORIG.Name = "CH_DEL_ORIG" + Me.CH_DEL_ORIG.Size = New System.Drawing.Size(135, 16) + Me.CH_DEL_ORIG.TabIndex = 0 + Me.CH_DEL_ORIG.Text = "Delete original file" + TT_MAIN.SetToolTip(Me.CH_DEL_ORIG, "If checked, the original file will be deleted after trimming") + Me.CH_DEL_ORIG.UseVisualStyleBackColor = True + ' + 'CH_ADD_M3U8 + ' + Me.CH_ADD_M3U8.AutoSize = True + Me.CH_ADD_M3U8.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_ADD_M3U8.Location = New System.Drawing.Point(146, 4) + Me.CH_ADD_M3U8.Name = "CH_ADD_M3U8" + Me.CH_ADD_M3U8.Size = New System.Drawing.Size(135, 16) + Me.CH_ADD_M3U8.TabIndex = 1 + Me.CH_ADD_M3U8.Text = "Add to M3U8" + TT_MAIN.SetToolTip(Me.CH_ADD_M3U8, "If checked, the trimmed files will be added to the M3U8 playlist (if selected)") + Me.CH_ADD_M3U8.UseVisualStyleBackColor = True + ' + 'CH_SEP_FOLDER + ' + Me.CH_SEP_FOLDER.AutoSize = True + Me.CH_SEP_FOLDER.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_SEP_FOLDER.Location = New System.Drawing.Point(288, 4) + Me.CH_SEP_FOLDER.Name = "CH_SEP_FOLDER" + Me.CH_SEP_FOLDER.Size = New System.Drawing.Size(136, 16) + Me.CH_SEP_FOLDER.TabIndex = 2 + Me.CH_SEP_FOLDER.Text = "Separate folder" + TT_MAIN.SetToolTip(Me.CH_SEP_FOLDER, "Place the trimmed files in a separate folder") + Me.CH_SEP_FOLDER.UseVisualStyleBackColor = True + ' + 'VideoOptionsTrimForm + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.ClientSize = New System.Drawing.Size(434, 261) + Me.Controls.Add(CONTAINER_MAIN) + Me.KeyPreview = True + Me.MinimizeBox = False + Me.MinimumSize = New System.Drawing.Size(450, 300) + Me.Name = "VideoOptionsTrimForm" + Me.ShowIcon = False + Me.ShowInTaskbar = False + Me.Text = "Trimming options" + CONTAINER_MAIN.ContentPanel.ResumeLayout(False) + CONTAINER_MAIN.ResumeLayout(False) + CONTAINER_MAIN.PerformLayout() + TP_MAIN.ResumeLayout(False) + TP_OPTIONS.ResumeLayout(False) + TP_OPTIONS.PerformLayout() + Me.ResumeLayout(False) + + End Sub + + Private WithEvents LIST_TRIM As ListBox + Private WithEvents CH_DEL_ORIG As CheckBox + Private WithEvents CH_ADD_M3U8 As CheckBox + Private WithEvents CH_SEP_FOLDER As CheckBox + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/VideoOptionsTrimForm.resx b/SCrawler.YouTube/Controls/VideoOptionsTrimForm.resx new file mode 100644 index 0000000..a8b04c2 --- /dev/null +++ b/SCrawler.YouTube/Controls/VideoOptionsTrimForm.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + 17, 17 + + \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/VideoOptionsTrimForm.vb b/SCrawler.YouTube/Controls/VideoOptionsTrimForm.vb new file mode 100644 index 0000000..c1cc922 --- /dev/null +++ b/SCrawler.YouTube/Controls/VideoOptionsTrimForm.vb @@ -0,0 +1,167 @@ +' Copyright (C) Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Forms.Toolbars +Imports SCrawler.API.YouTube.Objects +Imports SCrawler.API.YouTube.Base +Imports ETC = PersonalUtilities.Forms.Toolbars.EditToolbar.ControlItem +Namespace API.YouTube.Controls + Friend Class VideoOptionsTrimForm +#Region "Declarations" + Private WithEvents MyDefs As DefaultFormOptions + Private ReadOnly Property MyMedia As YouTubeMediaContainerBase + Private ReadOnly Property TrimData As List(Of TrimOption) + Private WithEvents BTT_CLEAR_ALL As ToolStripButton + Private WithEvents BTT_CHAPTERS As ToolStripButton +#End Region +#Region "Initializer" + Friend Sub New(ByRef Media As YouTubeMediaContainerBase) + InitializeComponent() + MyDefs = New DefaultFormOptions(Me, MyYouTubeSettings.DesignXml) + MyMedia = Media + TrimData = New List(Of TrimOption) + TrimData.ListAddList(Media.TrimOptions) + BTT_CLEAR_ALL = New ToolStripButton("Clear", PersonalUtilities.My.Resources.DeletePic_Red_24) With { + .DisplayStyle = ToolStripItemDisplayStyle.ImageAndText, + .BackColor = MyColor.DeleteBack, + .ForeColor = MyColor.DeleteFore + } + BTT_CHAPTERS = New ToolStripButton("Chapters") With {.DisplayStyle = ToolStripItemDisplayStyle.Text, .Enabled = Media.Chapters.Count > 0} + End Sub +#End Region +#Region "Form handlers" + Private Sub VideoOptionsTrimForm_Load(sender As Object, e As EventArgs) Handles Me.Load + With MyDefs + .MyViewInitialize() + .AddOkCancelToolbar() + .AddEditToolbar({ETC.Add, ETC.Edit, ETC.Delete, BTT_CLEAR_ALL, ETC.Separator, BTT_CHAPTERS}) + + Refill() + + With MyMedia + If Not .TrimOptionsSet Then + With MyYouTubeSettings + CH_DEL_ORIG.Checked = .TrimDeleteOriginalFile + CH_ADD_M3U8.Checked = .TrimAddTrimmedFilesToM3U8 + CH_SEP_FOLDER.Checked = .TrimSeparateFolder + End With + Else + CH_DEL_ORIG.Checked = .TrimDeleteOriginalFile + CH_ADD_M3U8.Checked = .TrimAddTrimmedFilesToM3U8 + CH_SEP_FOLDER.Checked = .TrimSeparateFolder + End If + End With + + .EndLoaderOperations() + End With + End Sub + Private Sub VideoOptionsTrimForm_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed + TrimData.Clear() + End Sub +#End Region +#Region "Refill" + Private Sub Refill(Optional ByVal DEL As Boolean = False) + Dim indx% = _CurrentIndex + With LIST_TRIM + .BeginUpdate() + .Items.Clear() + If TrimData.Count > 0 Then + TrimData.Sort() + .Items.AddRange(TrimData.Cast(Of Object).ToArray) + If DEL Then indx -= IIf(indx - 1 < 0, 0, 1) + If indx.ValueBetween(0, TrimData.Count - 1) Then + .SelectedIndex = indx + ElseIf (indx - 1).ValueBetween(0, TrimData.Count - 1) Then + .SelectedIndex = indx - 1 + ElseIf (indx + 1).ValueBetween(0, TrimData.Count - 1) Then + .SelectedIndex = indx + 1 + End If + End If + .EndUpdate() + End With + End Sub +#End Region +#Region "Ok" + Private Sub MyDefs_ButtonOkClick(ByVal Sender As Object, ByVal e As KeyHandleEventArgs) Handles MyDefs.ButtonOkClick + With MyMedia + .TrimOptions.Clear() + .TrimDeleteOriginalFile = CH_DEL_ORIG.Checked + .TrimAddTrimmedFilesToM3U8 = CH_ADD_M3U8.Checked + .TrimSeparateFolder = CH_SEP_FOLDER.Checked + .TrimOptionsSet = True + End With + + If TrimData.Count > 0 Then + TrimData.Sort() + Dim indx% = 0 + Dim c% + Dim opt As TrimOption + Dim dic As New Dictionary(Of String, Integer) + For i% = 0 To TrimData.Count - 1 + opt = TrimData(i) + If opt.Name.IsEmptyString Then + indx += 1 + opt.Name = indx + Else + c = TrimData.LongCount(Function(d) Not d.Name.IsEmptyString AndAlso d.Name = opt.Name) + If c > 1 Then + If Not dic.ContainsKey(opt.Name) Then dic.Add(opt.Name, 0) + dic(opt.Name) += 1 + opt.Name &= $"_{dic(opt.Name)}" + End If + End If + TrimData(i) = opt + Next + dic.Clear() + MyMedia.TrimOptions.AddRange(TrimData) + End If + + MyDefs.CloseForm() + End Sub +#End Region +#Region "Edit" + Private Sub MyDefs_ButtonAddClick(ByVal Sender As Object, ByVal e As EditToolbarEventArgs) Handles MyDefs.ButtonAddClick + Using f As New TrimOptionForm + f.ShowDialog() + If f.DialogResult = DialogResult.OK AndAlso + (TrimData.Count = 0 OrElse Not TrimData.Any(Function(t) t.End = f.MyTrimOption.End And t.Start = f.MyTrimOption.Start)) Then _ + TrimData.Add(f.MyTrimOption) : Refill() + End Using + End Sub + Private Sub MyDefs_ButtonEditClick(ByVal Sender As Object, ByVal e As EditToolbarEventArgs) Handles MyDefs.ButtonEditClick + If _CurrentIndex.ValueBetween(0, TrimData.Count - 1) Then + Using f As New TrimOptionForm(TrimData(_CurrentIndex)) + f.ShowDialog() + If f.DialogResult = DialogResult.OK Then TrimData(_CurrentIndex) = f.MyTrimOption : Refill() + End Using + End If + End Sub + Private Sub MyDefs_ButtonDeleteClickE(ByVal Sender As Object, ByVal e As EditToolbarEventArgs) Handles MyDefs.ButtonDeleteClickE + If _CurrentIndex.ValueBetween(0, TrimData.Count - 1) AndAlso + MsgBoxE({$"Are you sure you want to remove the following trim option?{vbCr}{vbCr}{TrimData(_CurrentIndex)}", "Remove trim option"}, vbYesNo + vbExclamation) = vbYes Then _ + TrimData.RemoveAt(_CurrentIndex) : Refill(True) + End Sub + Private Sub BTT_CLEAR_ALL_Click(sender As Object, e As EventArgs) Handles BTT_CLEAR_ALL.Click + If MsgBoxE({$"Are you sure you want to remove ALL trim options?", "Remove trim option"}, vbYesNo + vbCritical) = vbYes Then TrimData.Clear() : Refill() + End Sub + Private Sub BTT_CHAPTERS_Click(sender As Object, e As EventArgs) Handles BTT_CHAPTERS.Click + Using f As New ChaptersForm(MyMedia, TrimData) + f.ShowDialog() + If f.DialogResult = DialogResult.OK AndAlso f.MyResult.Count > 0 Then TrimData.ListAddList(f.MyResult, LAP.NotContainsOnly) : Refill() + End Using + End Sub +#End Region +#Region "List" + Private _CurrentIndex As Integer = -1 + Private Sub LIST_TRIM_SelectedIndexChanged(sender As Object, e As EventArgs) Handles LIST_TRIM.SelectedIndexChanged + _CurrentIndex = LIST_TRIM.SelectedIndex + End Sub +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Declarations.vb b/SCrawler.YouTube/Declarations.vb index ac6aff2..ac3e20e 100644 --- a/SCrawler.YouTube/Declarations.vb +++ b/SCrawler.YouTube/Declarations.vb @@ -54,6 +54,7 @@ Namespace API.YouTube Friend ReadOnly DateBaseProvider As New ADateTime(ADateTime.Formats.BaseDateTime) Friend ReadOnly DateAddedProvider As New ADateTime(ADateTime.Formats.yyyymmdd) With {.DateSeparator = String.Empty} Friend ReadOnly TimeToStringProvider As IFormatProvider = New TimeToStringConverter + Friend ReadOnly TimeToStringProviderH As IFormatProvider = New TimeToStringConverter(True) Friend ReadOnly TitleHtmlConverter As Func(Of String, String) = Function(Input) Input.StringRemoveWinForbiddenSymbols().StringTrim() Friend ReadOnly ProgressProvider As IMyProgressNumberProvider = MyProgressNumberProvider.Percentage Public ReadOnly TrueUrlRegEx As RParams = RParams.DM(Base.YouTubeFunctions.TrueUrlPattern, 0, EDP.ReturnValue) @@ -80,11 +81,15 @@ Namespace API.YouTube Private Class TimeToStringConverter : Implements ICustomProvider Private ReadOnly _Provider As New ADateTime("mm\:ss") With {.TimeParseMode = ADateTime.TimeModes.TimeSpan} Private ReadOnly _ProviderWithHours As New ADateTime("h\:mm\:ss") With {.TimeParseMode = ADateTime.TimeModes.TimeSpan} + Private ReadOnly ForceHours As Boolean Private ReadOnly Property Provider(ByVal t As TimeSpan) As IFormatProvider Get - Return If(t.Hours > 0, _ProviderWithHours, _Provider) + Return If(t.Hours > 0 Or ForceHours, _ProviderWithHours, _Provider) End Get End Property + Friend Sub New(Optional ByVal ForceHours As Boolean = False) + Me.ForceHours = ForceHours + End Sub Private Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object Implements ICustomProvider.Convert If Not IsNothing(Value) Then diff --git a/SCrawler.YouTube/Downloader/VideoListForm.vb b/SCrawler.YouTube/Downloader/VideoListForm.vb index 8562360..c56d52d 100644 --- a/SCrawler.YouTube/Downloader/VideoListForm.vb +++ b/SCrawler.YouTube/Downloader/VideoListForm.vb @@ -336,6 +336,7 @@ Namespace DownloadObjects.STDownloader pForm.Dispose() End If If Not c Is Nothing Then + If Not c.HasElements Then DirectCast(c, YouTubeMediaContainerBase).FileForceArtist() Dim f As Form Select Case c.ObjectType Case YouTubeMediaType.Single : f = New VideoOptionsForm(c) With {.UseCookies = useCookies} diff --git a/SCrawler.YouTube/My Project/AssemblyInfo.vb b/SCrawler.YouTube/My Project/AssemblyInfo.vb index c0e9251..4d40744 100644 --- a/SCrawler.YouTube/My Project/AssemblyInfo.vb +++ b/SCrawler.YouTube/My Project/AssemblyInfo.vb @@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices ' by using the '*' as shown below: ' - - + + diff --git a/SCrawler.YouTube/Objects/Track.vb b/SCrawler.YouTube/Objects/Track.vb index 4de6382..60df9ef 100644 --- a/SCrawler.YouTube/Objects/Track.vb +++ b/SCrawler.YouTube/Objects/Track.vb @@ -30,6 +30,11 @@ Namespace API.YouTube.Objects _File = CleanFileName(_File) End If End Sub + Protected Friend Overrides Sub FileForceArtist() + Dim __artistName$ = UserTitle.IfNullOrEmpty(AccountName) + If Not _File.Name.IsEmptyString AndAlso Not _File.Name.ToLower.Contains(__artistName.ToLower) Then _ + _File.Name = $"{__artistName} - {_File.Name}" + End Sub Public Overrides Function ToString(ByVal ForMediaItem As Boolean) As String Dim s$ = SizeStr If Not s.IsEmptyString Then s = $" [{s}]" diff --git a/SCrawler.YouTube/Objects/YouTubeMediaContainerBase.vb b/SCrawler.YouTube/Objects/YouTubeMediaContainerBase.vb index ce12c00..9cde71c 100644 --- a/SCrawler.YouTube/Objects/YouTubeMediaContainerBase.vb +++ b/SCrawler.YouTube/Objects/YouTubeMediaContainerBase.vb @@ -356,6 +356,14 @@ Namespace API.YouTube.Objects End If End Sub #End Region +#Region "Chapters, Trimming" + Friend ReadOnly Property Chapters As List(Of TrimOption) + Friend ReadOnly Property TrimOptions As List(Of TrimOption) + Friend Property TrimDeleteOriginalFile As Boolean = False + Friend Property TrimAddTrimmedFilesToM3U8 As Boolean = False + Friend Property TrimSeparateFolder As Boolean = False + Friend Property TrimOptionsSet As Boolean = False +#End Region #Region "IUserMedia Support" Private Property Attempts As Integer Implements IUserMedia.Attempts Private _Object As Object = Nothing @@ -684,6 +692,8 @@ Namespace API.YouTube.Objects End If End Set End Property + Protected Friend Overridable Sub FileForceArtist() + End Sub Friend Sub FileDateUpdate() Dim n$ = _File.Name.StringTrim Dim s$ = IIf(n.IsEmptyString, String.Empty, " ") @@ -826,8 +836,10 @@ Namespace API.YouTube.Objects 'yt-dlp 2025.07.21 'If Not MyYouTubeSettings.ReplaceModificationDate Then cmd &= " --no-mtime" cmd &= $" --{IIf(MyYouTubeSettings.ReplaceModificationDate.Value, String.Empty, "no-")}mtime" + If MyYouTubeSettings.DefaultVideoEmbedChapters Then cmd &= " --embed-chapters --add-chapters" cmd.StringAppend(formats, " ") cmd.StringAppend(subs, " ") + If MyYouTubeSettings.ErrorsIgnore Then cmd &= " --no-abort-on-error --ignore-errors" cmd.StringAppend(YouTubeFunctions.GetCookiesCommand(WithCookies, YouTubeCookieNetscapeFile), " ") cmd &= $" {URL} -o ""{File.PathWithSeparator}{File.Name}""" File.Exists(SFO.Path, True) @@ -845,6 +857,8 @@ Namespace API.YouTube.Objects _Subtitles = New List(Of Subtitles) _SubtitlesDelegated = New List(Of Subtitles) SubtitlesSelectedIndexes = New List(Of Integer) + Chapters = New List(Of TrimOption) + TrimOptions = New List(Of TrimOption) MediaObjects = New List(Of MediaObject) _Files = New List(Of SFile) @@ -1267,6 +1281,7 @@ Namespace API.YouTube.Objects Dim fPatternFiles$ = $"{File.Name}*." & "{0}" Dim fAacAudio As New TempFileConversion(New SFile(String.Format(fPattern, aac)), Me) Dim mp3ThumbEmbedded As Boolean = False + Dim audioFiles As New List(Of SFile) Dim tempFilesList As New List(Of TempFileConversion) Dim ttFile As TempFileConversion @@ -1374,13 +1389,17 @@ Namespace API.YouTube.Objects format = format.StringToLower f = String.Format(fPattern, format) AddFile(f) + audioFiles.Add(f) If Not f.Exists Then tryToConvert.Invoke(format, f) updateBitrate(f) - If format = mp3 And Not mp3ThumbEmbedded And MyYouTubeSettings.DefaultAudioEmbedThumbnail_ExtractedFiles Then _ - embedThumbTo.Invoke(f) : mp3ThumbEmbedded = True - If Not M3U8_PlaylistFiles.ListExists AndAlso f.Exists Then M3U8_Append(f) - If format = mp3 AndAlso f.Exists AndAlso MyYouTubeSettings.VideoPlaylist_AddExtractedMP3.Value Then M3U8_Append(f) + If f.Exists Then + If format = mp3 And Not mp3ThumbEmbedded And MyYouTubeSettings.DefaultAudioEmbedThumbnail_ExtractedFiles Then _ + embedThumbTo.Invoke(f) : mp3ThumbEmbedded = True + If M3U8_PlaylistFiles.ListExists OrElse + (format = mp3 AndAlso MyYouTubeSettings.VideoPlaylist_AddExtractedMP3.Value) Then _ + M3U8_Append(f) + End If End If Next End If @@ -1433,6 +1452,43 @@ Namespace API.YouTube.Objects If OutputVideoFPS > 0 AndAlso SelectedVideo.Bitrate <> OutputVideoFPS Then _ reencodeFile("ffmpeg -i ""{0}"" -filter:v fps=" & OutputVideoFPS.ToString.Replace(", ", ".") & " -c:a copy ""{1}""") End If + + 'Trimming + If TrimOptions.Count > 0 AndAlso File.Exists Then + Const trimCommand$ = "ffmpeg -i ""{0}"" -ss {1} -to {2} -c:v copy -c:a copy ""{3}""" + Dim trimFirstFile As SFile = Nothing + Dim trimFirstAdded As Boolean = False + Dim processTrim As Action(Of TrimOption, SFile) = + Sub(ByVal opt As TrimOption, ByVal pFile As SFile) + Dim fNew As SFile = pFile + fNew.Name &= $"_{opt.Name}" + If TrimSeparateFolder Then fNew = $"{fNew.PathNoSeparator}\{MyYouTubeSettings.TrimSeparateFolderName.Value.IfNullOrEmpty(YouTubeSettings.TrimSeparateFolderNameDefault)}\{fNew.File}" : fNew.Exists(SFO.Path) + format = fNew.Extension.StringToLower + .Execute(String.Format(trimCommand, + pFile, + AConvert(Of String)(opt.StartTime, TimeToStringProvider), + AConvert(Of String)(opt.EndTime, TimeToStringProvider), + fNew)) + If fNew.Exists Then + If trimFirstFile.IsEmptyString Then trimFirstFile = fNew + AddFile(fNew) + If format = mp3 And MyYouTubeSettings.DefaultAudioEmbedThumbnail_ExtractedFiles Then _ + embedThumbTo.Invoke(fNew) : mp3ThumbEmbedded = True + If (TrimAddTrimmedFilesToM3U8 Or (TrimDeleteOriginalFile And Not trimFirstAdded)) AndAlso + (M3U8_PlaylistFiles.ListExists OrElse + (format = mp3 AndAlso MyYouTubeSettings.VideoPlaylist_AddExtractedMP3.Value)) Then _ + M3U8_Append(fNew) : trimFirstAdded = True + End If + End Sub + For Each tr As TrimOption In TrimOptions + processTrim(tr, File) + If audioFiles.Count > 0 Then + For Each f In audioFiles : processTrim(tr, f) : Next + End If + Next + If TrimDeleteOriginalFile Then File.Delete() : File = trimFirstFile + + End If End If End If End With @@ -1692,6 +1748,8 @@ Namespace API.YouTube.Objects ParseThumbnails(.Self) ParseSubtitles(.Self) + + ParseChapters(.Self) End With Return True End If @@ -1957,6 +2015,15 @@ Namespace API.YouTube.Objects End With End If End Sub + Protected Sub ParseChapters(ByVal e As EContainer) + With e({"chapters"}) + If .ListExists Then Chapters.AddRange(.Select(Function(ee) New TrimOption With { + .Start = CInt(AConvert(Of Double)(ee.Value("start_time"), 0, EDP.ReturnValue)), + .[End] = CInt(AConvert(Of Double)(ee.Value("end_time"), 0, EDP.ReturnValue)), + .Name = CleanFileName(New SFile With {.Name = ee.Value("title")}).Name + })) + End With + End Sub #End Region #Region "IEContainerProvider Support" Private Function GetElementsChecked() As IEnumerable(Of EContainer) @@ -2031,6 +2098,8 @@ Namespace API.YouTube.Objects _Subtitles.Clear() _SubtitlesDelegated.Clear() SubtitlesSelectedIndexes.Clear() + Chapters.Clear() + TrimOptions.Clear() MediaObjects.Clear() _Files.Clear() PostProcessing_OutputAudioFormats.Clear() diff --git a/SCrawler.YouTube/SCrawler.YouTube.vbproj b/SCrawler.YouTube/SCrawler.YouTube.vbproj index 9bc194e..a0d7dad 100644 --- a/SCrawler.YouTube/SCrawler.YouTube.vbproj +++ b/SCrawler.YouTube/SCrawler.YouTube.vbproj @@ -124,6 +124,12 @@ Form + + ChaptersForm.vb + + + Form + FilterForm.vb @@ -136,6 +142,18 @@ Form + + TrimOptionForm.vb + + + Form + + + VideoOptionsTrimForm.vb + + + Form + @@ -227,12 +245,21 @@ ChannelTabsChooserForm.vb + + ChaptersForm.vb + FilterForm.vb PlayListParserForm.vb + + TrimOptionForm.vb + + + VideoOptionsTrimForm.vb + MediaItem.vb diff --git a/SCrawler.YouTubeDownloader/My Project/AssemblyInfo.vb b/SCrawler.YouTubeDownloader/My Project/AssemblyInfo.vb index 3bcb338..f2e4108 100644 --- a/SCrawler.YouTubeDownloader/My Project/AssemblyInfo.vb +++ b/SCrawler.YouTubeDownloader/My Project/AssemblyInfo.vb @@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices ' by using the '*' as shown below: ' - - + + diff --git a/SCrawler.sln b/SCrawler.sln index ff2c703..3f45485 100644 --- a/SCrawler.sln +++ b/SCrawler.sln @@ -29,6 +29,8 @@ Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "SCrawler.Updater", "SCrawle EndProject Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "SCrawler.Shared", "SCrawler.Shared\SCrawler.Shared.vbproj", "{DC634700-24C7-42DD-BF8F-87E6CC54E625}" EndProject +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "PersonalUtilities.Images", "..\..\MyUtilities\PersonalUtilities.Images\PersonalUtilities.Images.vbproj", "{B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -135,6 +137,18 @@ Global {DC634700-24C7-42DD-BF8F-87E6CC54E625}.Release|x64.Build.0 = Release|x64 {DC634700-24C7-42DD-BF8F-87E6CC54E625}.Release|x86.ActiveCfg = Release|x86 {DC634700-24C7-42DD-BF8F-87E6CC54E625}.Release|x86.Build.0 = Release|x86 + {B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}.Debug|x64.ActiveCfg = Debug|x64 + {B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}.Debug|x64.Build.0 = Debug|x64 + {B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}.Debug|x86.ActiveCfg = Debug|x86 + {B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}.Debug|x86.Build.0 = Debug|x86 + {B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}.Release|Any CPU.Build.0 = Release|Any CPU + {B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}.Release|x64.ActiveCfg = Release|x64 + {B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}.Release|x64.Build.0 = Release|x64 + {B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}.Release|x86.ActiveCfg = Release|x86 + {B7EF76A9-96F3-4C53-B252-0AB5F79B67B3}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SCrawler/API/Base/UserDataBase.vb b/SCrawler/API/Base/UserDataBase.vb index 60e383e..62a37f3 100644 --- a/SCrawler/API/Base/UserDataBase.vb +++ b/SCrawler/API/Base/UserDataBase.vb @@ -1723,6 +1723,7 @@ BlockNullPicture: Dim vsf As Boolean = SeparateVideoFolderF Dim __isVideo As Boolean Dim __interrupt As Boolean + Dim postProcessWebp As Boolean Dim f As SFile, fTxt As SFile Dim v As UserMedia Dim __fileDeleted As Boolean @@ -1782,6 +1783,7 @@ BlockNullPicture: If v.URL_BASE.IsEmptyString Then v.URL_BASE = v.URL __fileDeleted = False + postProcessWebp = False If (v.Type = UTypes.Text And DownloadText) Or (Not f.IsEmptyString And Not v.URL.IsEmptyString) Then Try @@ -1794,8 +1796,9 @@ BlockNullPicture: Case UTypes.Video, UTypes.m3u8 : f.Extension = "mp4" Case UTypes.GIF : f.Extension = "gif" End Select - ElseIf f.Extension = "webp" And Settings.DownloadNativeImageFormat Then - f.Extension = "jpg" + ElseIf f.Extension = UserImage.ExtWebp And Settings.DownloadNativeImageFormat And Settings.FfmpegFile.Exists Then + 'f.Extension = "jpg" + postProcessWebp = True End If If Not v.SpecialFolder.IsEmptyString Then @@ -1830,7 +1833,7 @@ BlockNullPicture: updateDownCount(False) - v.File = ChangeFileNameByProvider(f, v) + v.File = DownloadContentDefault_ConvertWebp(ChangeFileNameByProvider(f, v), postProcessWebp) v.State = UStates.Downloaded DownloadContentDefault_PostProcessing(v, f, Token) If UseMD5Comparison And (v.Type = UTypes.GIF Or v.Type = UTypes.Picture) Then @@ -1930,6 +1933,22 @@ stxt: End Function Protected Overridable Sub DownloadContentDefault_PostProcessing(ByRef m As UserMedia, ByVal File As SFile, ByVal Token As CancellationToken) End Sub + Protected Overridable Function DownloadContentDefault_ConvertWebp(ByVal WebpFile As SFile, ByVal Process As Boolean) As SFile + Dim f As SFile = WebpFile + If Process AndAlso f.Exists Then + f.Path = $"{f.PathWithSeparator}Sources" + f.Exists(SFO.Path) + If WebpFile.Copy(f) Then + Dim newFile As SFile = WebpFile + newFile.Extension = UserImage.ExtJpg + f = UserImage.ConvertWebp(f, newFile) + If f.Exists Then WebpFile.Delete(SFO.File, SFODelete.DeletePermanently, EDP.ReturnValue) + Else + f = WebpFile + End If + End If + Return f + End Function Protected Overridable Function DownloadContentDefault_ProcessDownloadException() As Boolean Return True End Function diff --git a/SCrawler/API/Redgifs/SiteSettings.vb b/SCrawler/API/Redgifs/SiteSettings.vb index 061e18f..5b7a12a 100644 --- a/SCrawler/API/Redgifs/SiteSettings.vb +++ b/SCrawler/API/Redgifs/SiteSettings.vb @@ -64,11 +64,16 @@ Namespace API.RedGifs Case NameOf(Token) : Responser.Headers.Add(TokenName, Value) Case NameOf(UserAgent) : Responser.UserAgent = Value End Select - Responser.SaveSettings() + Responser.SaveSettings(, New ErrorsDescriber(EDP.ReturnValue + If(_TokenUpdating, EDP.None, EDP.SendToLog))) End Sub #End Region #Region "Token updaters" + Private _TokenUpdating As Boolean = False Friend Function UpdateTokenIfRequired() As Boolean + While _TokenUpdating : Threading.Thread.Sleep(100) : End While + Return UpdateTokenIfRequired_Impl() + End Function + Private Function UpdateTokenIfRequired_Impl() As Boolean Dim d As Date? = AConvert(Of Date)(TokenLastDateUpdated.Value, AModes.Var, Nothing) If Not d.HasValue OrElse d.Value < Now.AddMinutes(-CInt(TokenUpdateInterval.Value)) Then Return UpdateToken() @@ -78,7 +83,12 @@ Namespace API.RedGifs End Function Friend Function UpdateToken() As Boolean + While _TokenUpdating : Threading.Thread.Sleep(100) : End While + Return UpdateToken_Impl() + End Function + Private Function UpdateToken_Impl() As Boolean Try + _TokenUpdating = True Dim r$ Dim NewToken$ = String.Empty, NewAgent$ = String.Empty Using resp As New Responser : r = resp.GetResponse("https://api.redgifs.com/v2/auth/temporary",, EDP.ThrowException) : End Using @@ -100,6 +110,8 @@ Namespace API.RedGifs End If Catch ex As Exception Return ErrorsDescriber.Execute(EDP.SendToLog, ex, "[API.RedGifs.SiteSettings.UpdateToken]", False) + Finally + _TokenUpdating = False End Try End Function #End Region diff --git a/SCrawler/Download/Feed/FeedMedia.vb b/SCrawler/Download/Feed/FeedMedia.vb index 812294c..102db68 100644 --- a/SCrawler/Download/Feed/FeedMedia.vb +++ b/SCrawler/Download/Feed/FeedMedia.vb @@ -7,9 +7,10 @@ ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY Imports System.ComponentModel -Imports SCrawler.API.Base +Imports System.IO Imports PersonalUtilities.Forms Imports PersonalUtilities.Tools +Imports SCrawler.API.Base Imports UserMediaD = SCrawler.DownloadObjects.TDownloader.UserMediaD Namespace DownloadObjects @@ -137,28 +138,18 @@ Namespace DownloadObjects End Sub #End Region #Region "Converter" - Private Const ExtWebp As String = "webp" - Private Const ExtJpg As String = "jpg" - Private Function ConvertWebp(ByVal file As SFile, Optional ByVal NewCacheDir As Boolean = False) As SFile - If file.Extension = ExtWebp Then - If Settings.FfmpegFile.Exists Then - Dim dir As SFile - If NewCacheDir Then dir = Settings.Cache.NewPath Else dir = Settings.Cache - Dim f As SFile = file - f.Path = dir.Path - f.Extension = ExtJpg - Using imgBatch As New BatchExecutor - With imgBatch - .ChangeDirectory(dir) - .Execute($"""{Settings.FfmpegFile}"" -i ""{file}"" ""{f}""") - End With - End Using - If f.Exists Then Return f - End If - Else - Return file - End If - Return Nothing + Private Const ExtWebp As String = UserImage.ExtWebp + Private Const ExtJpg As String = UserImage.ExtJpg + Private Function ConvertOptional(ByVal file As SFile, ByVal GetError As Boolean, ByRef IsWebP As Boolean) As ImageRenderer + Dim ir As ImageRenderer2 = Nothing + Try + ir = New ImageRenderer2(file, EDP.ThrowException) + If ir.HasError Then Throw If(ir.ImgErr, New Exception) Else Return ir + Catch ex As Exception + IsWebP = ir?.NativeFormat.IfNullOrEmpty(ExtJpg) = ExtWebp + ir.DisposeIfReady + If GetError Then Throw ex Else Return Nothing + End Try End Function #End Region #Region "Initializers" @@ -174,6 +165,42 @@ Namespace DownloadObjects Public Sub New() InitializeComponent() End Sub + Private Class ImageRenderer2 : Inherits ImageRenderer + Friend NativeFormat As String = Nothing + Friend ImgErr As Exception = Nothing + Friend Sub New(ByVal ImgPath As SFile, Optional ByVal e As ErrorsDescriber = Nothing) + MyBase.New() + Try + If ImgPath.Exists(SFO.File, False) Then + OriginalImageBytes = SFile.GetBytes(ImgPath, EDP.ThrowException) + Try + OriginalImage = GetImage(OriginalImageBytes) + Catch exInternal As Exception + HasError = True + ImgErr = exInternal + NativeFormat = GetTrueFormat(OriginalImageBytes, EDP.ReturnValue) + End Try + End If + Address = ImgPath + Catch ex As Exception + HasError = True + NativeFormat = GetTrueFormat(OriginalImageBytes, EDP.ReturnValue) + If Not e.Exists Then e = EDP.ThrowException + ErrorsDescriber.Execute(e, ex, $"ImageRenderer2.New({ImgPath})") + End Try + End Sub + Friend Shared Function GetTrueFormat(ByVal Img() As Byte, Optional ByVal e As ErrorsDescriber = Nothing) As String + Try + Using ms As New MemoryStream(Img, 0, Img.Length) + Return System.Windows.Media.Imaging.BitmapDecoder.Create(ms, Windows.Media.Imaging.BitmapCreateOptions.PreservePixelFormat, + Windows.Media.Imaging.BitmapCacheOption.OnLoad).Metadata.Format + End Using + Catch ex As Exception + If Not e.Exists Then e = EDP.ThrowException + Return ErrorsDescriber.Execute(e, ex, "[ImageRenderer2.GetTrueFormat()]") + End Try + End Function + End Class Friend Sub New(ByVal Media As UserMediaD, ByVal Width As Integer, ByVal Height As Integer, ByVal IsSession As Boolean, ByVal ExtractText As Boolean) Try InitializeComponent() @@ -211,7 +238,7 @@ Namespace DownloadObjects End With If Not imgFile.Exists Then Settings.Cache.Validate() - If GetWebFile(Media.Data.URL, imgFile, EDP.None) AndAlso imgFile.Exists Then File = ConvertWebp(imgFile) + If GetWebFile(Media.Data.URL, imgFile, EDP.None) AndAlso imgFile.Exists Then File = UserImage.ConvertWebp(imgFile, Nothing) Else File = imgFile End If @@ -260,10 +287,17 @@ Namespace DownloadObjects End If End If End If - tmpMediaFile = ConvertWebp(tmpMediaFile, True) + Dim webpConverted As Boolean = False + Dim isWebp As Boolean = False + tmpMediaFile = UserImage.ConvertWebp(tmpMediaFile, Nothing,,, webpConverted) If tmpMediaFile.IsEmptyString Then Throw New ArgumentNullException With {.HelpLink = 1} Try - MyImage = New ImageRenderer(tmpMediaFile, EDP.ThrowException) + For kConv As Byte = 0 To 1 + If kConv = 1 Then tmpMediaFile = UserImage.ConvertWebp(tmpMediaFile, Nothing, True, isWebp, webpConverted) + If Not tmpMediaFile.IsEmptyString Then MyImage = ConvertOptional(tmpMediaFile, kConv = 1 Or webpConverted, isWebp) + If Not MyImage Is Nothing Then Exit For + Next + If MyImage Is Nothing Then Throw New Exception Catch MyImage.DisposeIfReady MyImage = New ImageRenderer(New Bitmap(10, 10)) diff --git a/SCrawler/My Project/AssemblyInfo.vb b/SCrawler/My Project/AssemblyInfo.vb index a90cd29..8684323 100644 --- a/SCrawler/My Project/AssemblyInfo.vb +++ b/SCrawler/My Project/AssemblyInfo.vb @@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices ' by using the '*' as shown below: ' - - + + diff --git a/SCrawler/SCrawler.vbproj b/SCrawler/SCrawler.vbproj index b94e128..715bc07 100644 --- a/SCrawler/SCrawler.vbproj +++ b/SCrawler/SCrawler.vbproj @@ -128,6 +128,7 @@ ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll + @@ -142,6 +143,7 @@ + @@ -713,6 +715,10 @@ + + {b7ef76a9-96f3-4c53-b252-0ab5f79b67b3} + PersonalUtilities.Images + {fc532253-1ab3-4def-a28a-dfdd9a481eb2} PersonalUtilities.Notifications diff --git a/SCrawler/UserImage.vb b/SCrawler/UserImage.vb index 58bdbb0..48522c8 100644 --- a/SCrawler/UserImage.vb +++ b/SCrawler/UserImage.vb @@ -6,6 +6,7 @@ ' ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY +Imports System.IO Imports PersonalUtilities.Tools Friend Class UserImage : Inherits ImageRenderer Friend Const ImagePrefix As String = "UserPicture" @@ -97,4 +98,56 @@ Friend Class UserImage : Inherits ImageRenderer Friend Overloads Shared Function CreateImageFromText(ByVal Text As String, ByVal File As SFile) As Boolean Return CreateImageFromText(Text, FormFont, Color.Black, TextImageWidth, File, Color.White,, EDP.ThrowException) End Function + Friend Const ExtWebp As String = "webp" + Friend Const ExtJpg As String = "jpg" + Friend Shared Function ConvertWebp(ByVal InitFile As SFile, ByVal DestFile As SFile, + Optional ByVal Force As Boolean = False, Optional ByVal ToWebP As Boolean = False, + Optional ByRef Result As Boolean = False) As SFile + Result = False + If InitFile.Extension = ExtWebp Or Force Then + If Settings.FfmpegFile.Exists Or ToWebP Then + Dim dir As SFile + Dim postfix$ = String.Empty + If DestFile.IsEmptyString Then + dir = $"{Settings.Cache.RootDirectory.PathWithSeparator}ConvWebp\" + Settings.Cache.AddPath(dir) + postfix = $"_{InitFile.GetHashCode}" + Else + dir = New SFile With {.Path = DestFile.Path} + End If + dir.Exists(SFO.Path) + Dim f As SFile = DestFile.IfNullOrEmpty(InitFile) + f.Path = dir.PathNoSeparator + If Not postfix.IsEmptyString Then f.Name &= postfix + f.Extension = IIf(ToWebP, ExtWebp, ExtJpg) + + If DestFile.IsEmptyString AndAlso f.Exists AndAlso Not f.Extension = ExtWebp Then Return f + + If ToWebP Then + If Not f.Exists Then InitFile.Copy(f) + If f.Exists Then + InitFile = f + f.Extension = ExtJpg + Else + Throw New ArgumentNullException With {.HelpLink = 1} + End If + End If + If Not ConvertWebpTryImageMagick(InitFile, f) Then + Using imgBatch As New BatchExecutor + With imgBatch + .ChangeDirectory(dir) + .Execute($"""{Settings.FfmpegFile}"" -i ""{InitFile}"" ""{f}""") + End With + End Using + End If + If f.Exists Then Result = True : Return f + End If + Else + Return InitFile + End If + Return Nothing + End Function + Private Shared Function ConvertWebpTryImageMagick(ByVal InitFile As SFile, ByVal DestFile As SFile) As Boolean + Return ImageRendererExt.ConvertWebp(InitFile, DestFile, EDP.SendToLog + EDP.ReturnValue) + End Function End Class \ No newline at end of file