Compare commits
No commits in common. "255cf9b422e2024c13226217ee7c4a0f22fb78f4" and "4d5713fed14556c4f79b67d3c9276bb4eac36430" have entirely different histories.
255cf9b422
...
4d5713fed1
38 changed files with 1 additions and 7272 deletions
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
@ -1,13 +0,0 @@
|
|||
<Project Synchronized="False" SyncFilter="*.ps1;*.psm1;*.psd1;*.ps1xml;*.psf;*.pss;*.xml;*.help.txt">
|
||||
<Version>2.1</Version>
|
||||
<FileID>cfce16fa-6b4e-4338-a517-88dba9b07e2c</FileID>
|
||||
<ProjectType>0</ProjectType>
|
||||
<RememberPowerShellVersion>True</RememberPowerShellVersion>
|
||||
<PowerShellVersion>Local Machine - PowerShell V5 (64 Bit)</PowerShellVersion>
|
||||
<Folders />
|
||||
<Files>
|
||||
<File Build="0">Startup.pss</File>
|
||||
<File Build="0" Shared="True" ReferenceFunction="Invoke-Globals_ps1">Globals.ps1</File>
|
||||
<File Build="0" ReferenceFunction="Show-MainForm_psf">MainForm.psf</File>
|
||||
</Files>
|
||||
</Project>
|
|
@ -1,11 +0,0 @@
|
|||
<Project Synchronized="False" SyncFilter="*.ps1;*.psm1;*.psd1;*.ps1xml;*.psf;*.pss;*.xml;*.help.txt">
|
||||
<Version>2.0</Version>
|
||||
<FileID>cfce16fa-6b4e-4338-a517-88dba9b07e2c</FileID>
|
||||
<ProjectType>0</ProjectType>
|
||||
<Folders />
|
||||
<Files>
|
||||
<File Build="0">Startup.pss</File>
|
||||
<File Build="0" Shared="True" ReferenceFunction="Invoke-Globals_ps1">Globals.ps1</File>
|
||||
<File Build="0" ReferenceFunction="Show-MainForm_psf">MainForm.psf</File>
|
||||
</Files>
|
||||
</Project>
|
Binary file not shown.
|
@ -1,9 +0,0 @@
|
|||
<ProjectState>
|
||||
<Version>1.0</Version>
|
||||
<FileID>cfce16fa-6b4e-4338-a517-88dba9b07e2c</FileID>
|
||||
<OpenFolders />
|
||||
<OpenFiles>
|
||||
<File>Globals.ps1</File>
|
||||
<File>MainForm.psf</File>
|
||||
</OpenFiles>
|
||||
</ProjectState>
|
212
Globals.ps1
212
Globals.ps1
|
@ -1,212 +0,0 @@
|
|||
<#
|
||||
.NOTES
|
||||
===========================================================================
|
||||
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.144
|
||||
Created on: 30/09/2017 06:23 PM
|
||||
Created by: Yuuki-chan
|
||||
Organization:
|
||||
Filename: Globals.ps1
|
||||
===========================================================================
|
||||
.DESCRIPTION
|
||||
A description of the file.
|
||||
#>
|
||||
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
|
||||
[XML]$doc = (New-Object System.Net.WebClient).DownloadString("http://update.yuuki-chan.xyz/Updater.xml")
|
||||
[string]$version1 = "2.0.1.0"
|
||||
[string]$version2 = $doc.Updater.AudioSplitterV2.Version
|
||||
[string]$downloadUrl = $doc.Updater.AudioSplitterV2.Url
|
||||
$props = ConvertFrom-StringData (Get-Content .\res\config.txt -Raw)
|
||||
|
||||
function Unzip
|
||||
{
|
||||
param ([string]$zipfile, [string]$outpath) [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath)
|
||||
}
|
||||
|
||||
function Get-ScriptDirectory
|
||||
{
|
||||
[OutputType([string])]
|
||||
param ()
|
||||
if ($null -ne $hostinvocation)
|
||||
{
|
||||
Split-Path $hostinvocation.MyCommand.path
|
||||
}
|
||||
else
|
||||
{
|
||||
Split-Path $script:MyInvocation.MyCommand.Path
|
||||
}
|
||||
}
|
||||
|
||||
[string]$ScriptDirectory = Get-ScriptDirectory
|
||||
[Console]::WriteLine("Ignore (any) start-up errors. It's finnnneeee.")
|
||||
|
||||
function about
|
||||
{
|
||||
$pictureBox1 = New-Object System.Windows.Forms.PictureBox
|
||||
$pictureBox1.ImageLocation = "res/yuki.gif"
|
||||
$pictureBox1.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::StretchImage
|
||||
$pictureBox1.BorderStyle = [System.Windows.Forms.BorderStyle]::FixedSingle
|
||||
$pictureBox1.Size = New-Object System.Drawing.Size(570, 405)
|
||||
$pictureBox1.Location = New-Object System.Drawing.Point(3, 3)
|
||||
|
||||
$label1 = New-Object System.Windows.Forms.Label
|
||||
$label1.Text = "Original tool by:"
|
||||
$label1.AutoSize = $true
|
||||
$label1.Location = New-Object System.Drawing.Point(16, 413)
|
||||
|
||||
$label2 = New-Object System.Windows.Forms.Label
|
||||
$label2.Text = "GUI design by:"
|
||||
$label2.AutoSize = $true
|
||||
$label2.Location = New-Object System.Drawing.Point(23, 443)
|
||||
|
||||
$label3 = New-Object System.Windows.Forms.Label
|
||||
$label3.Text = "Special thanks to:"
|
||||
$label3.AutoSize = $true
|
||||
$label3.Location = New-Object System.Drawing.Point(0, 473)
|
||||
|
||||
$label4 = New-Object System.Windows.Forms.Label
|
||||
$label4.Text = "PowerShell code:"
|
||||
$label4.AutoSize = $true
|
||||
$label4.Location = New-Object System.Drawing.Point(3, 502)
|
||||
|
||||
$label5 = New-Object System.Windows.Forms.Label
|
||||
$label5.Text = "(v1.0.0.1)"
|
||||
$label5.AutoSize = $true
|
||||
$label5.Location = New-Object System.Drawing.Point(341, 443)
|
||||
|
||||
$linkLabel1 = New-Object System.Windows.Forms.LinkLabel
|
||||
$linkLabel1.Text = "RiCON"
|
||||
$linkLabel1.AutoSize = $true
|
||||
$linkLabel1.Location = New-Object System.Drawing.Point(152, 413)
|
||||
$linkLabel1.Add_Click({
|
||||
[System.Diagnostics.Process]::Start("https://forum.doom9.org/member.php?u=48461")
|
||||
})
|
||||
|
||||
$linkLabel2 = New-Object System.Windows.Forms.LinkLabel
|
||||
$linkLabel2.Text = "VFR Chapter Creator 0.9"
|
||||
$linkLabel2.AutoSize = $true
|
||||
$linkLabel2.Location = New-Object System.Drawing.Point(276, 413)
|
||||
$linklabel2.Add_Click({
|
||||
[System.Diagnostics.Process]::Start("https://forum.doom9.org/showthread.php?t=154535")
|
||||
})
|
||||
|
||||
$linkLabel3 = New-Object System.Windows.Forms.LinkLabel
|
||||
$linkLabel3.Text = "Yuuki-chan"
|
||||
$linkLabel3.AutoSize = $true
|
||||
$linkLabel3.Location = New-Object System.Drawing.Point(152, 443)
|
||||
$linkLabel3.Add_Click({
|
||||
[System.Diagnostics.Process]::Start("https://yuuki-chan.xyz/")
|
||||
})
|
||||
|
||||
$linkLabel4 = New-Object System.Windows.Forms.LinkLabel
|
||||
$linkLabel4.Text = "[1001]"
|
||||
$linkLabel4.AutoSize = $true
|
||||
$linkLabel4.Location = New-Object System.Drawing.Point(276, 443)
|
||||
$linkLabel4.Add_Click({
|
||||
[System.Diagnostics.Process]::Start("https://yuuki-chan.xyz/")
|
||||
})
|
||||
|
||||
$linkLabel5 = New-Object System.Windows.Forms.LinkLabel
|
||||
$linkLabel5.Text = "Gabriel Logan"
|
||||
$linkLabel5.AutoSize = $true
|
||||
$linkLabel5.Location = New-Object System.Drawing.Point(152, 473)
|
||||
$linkLabel5.Add_Click({
|
||||
[System.Diagnostics.Process]::Start("http://www.mexat.com/vb/members/608471-Gabriel-Logan")
|
||||
})
|
||||
|
||||
$linkLabel6 = New-Object System.Windows.Forms.LinkLabel
|
||||
$linkLabel6.Text = "AmjadSONY"
|
||||
$linkLabel6.AutoSize = $true
|
||||
$linkLabel6.Location = New-Object System.Drawing.Point(276, 473)
|
||||
$linkLabel6.Add_Click({
|
||||
[System.Diagnostics.Process]::Start("http://www.mexat.com/vb/members/574111-%D8%A7%D9%85%D8%AC%D8%AF-%D8%B5%D9%84%D8%A7%D8%AD")
|
||||
})
|
||||
|
||||
$linkLabel7 = New-Object System.Windows.Forms.LinkLabel
|
||||
$linkLabel7.Text = "Intelligent"
|
||||
$linkLabel7.AutoSize = $true
|
||||
$linkLabel7.Location = New-Object System.Drawing.Point(399, 473)
|
||||
$linkLabel7.Add_Click({
|
||||
[System.Diagnostics.Process]::Start("http://www.mexat.com/vb/members/685155-Intelligent")
|
||||
})
|
||||
|
||||
$linkLabel8 = New-Object System.Windows.Forms.LinkLabel
|
||||
$linkLabel8.Text = "Yuuki-chan"
|
||||
$linkLabel8.AutoSize = $true
|
||||
$linkLabel8.Location = New-Object System.Drawing.Point(152, 502)
|
||||
$linkLabel8.Add_Click({
|
||||
[System.Diagnostics.Process]::Start("https://yuuki-chan.xyz/")
|
||||
})
|
||||
|
||||
$button1 = New-Object System.Windows.Forms.Button
|
||||
$button1.Text = "OK"
|
||||
$button1.Location = New-Object System.Drawing.Point(225, 527)
|
||||
$button1.Size = New-Object System.Drawing.Size(125, 35)
|
||||
$button1.Add_Click({
|
||||
$about.Close()
|
||||
})
|
||||
|
||||
$about = New-Object System.Windows.Forms.Form
|
||||
$about.Text = "About"
|
||||
$about.MaximizeBox = $false
|
||||
$about.MinimizeBox = $false
|
||||
$about.Size = New-Object System.Drawing.Size(592, 623)
|
||||
$about.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedSingle
|
||||
$about.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon(".\res\AS.ico")
|
||||
$about.Controls.Add($pictureBox1)
|
||||
$about.Controls.Add($label1)
|
||||
$about.Controls.Add($label2)
|
||||
$about.Controls.Add($label3)
|
||||
$about.Controls.Add($label4)
|
||||
$about.Controls.Add($label5)
|
||||
$about.Controls.Add($linkLabel1)
|
||||
$about.Controls.Add($linkLabel2)
|
||||
$about.Controls.Add($linkLabel3)
|
||||
$about.Controls.Add($linkLabel4)
|
||||
$about.Controls.Add($linkLabel5)
|
||||
$about.Controls.Add($linkLabel6)
|
||||
$about.Controls.Add($linkLabel7)
|
||||
$about.Controls.Add($linkLabel8)
|
||||
$about.Controls.Add($button1)
|
||||
$about.Select()
|
||||
$about.ShowDialog()
|
||||
}
|
||||
|
||||
function checkForUpdate
|
||||
{
|
||||
if ($version1 -eq $version2)
|
||||
{
|
||||
$msgBox = [System.Windows.Forms.MessageBox]::Show("You already have the latest version installed.", "Information", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
$WC = New-Object System.Net.WebClient
|
||||
$WC.DownloadFile($downloadUrl, $version2 + ".zip");
|
||||
# Unzip($version2 + ".zip", "")
|
||||
}
|
||||
catch
|
||||
{
|
||||
$em = $_.Exception.Message
|
||||
$msgBox = [System.Windows.Forms.MessageBox]::Show($em, "Information", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function forceUpdate
|
||||
{
|
||||
try
|
||||
{
|
||||
$WC = New-Object System.Net.WebClient
|
||||
$WC.DownloadFile($downloadUrl, $version2 + ".zip");
|
||||
# Unzip($version2 + ".zip", "")
|
||||
}
|
||||
catch
|
||||
{
|
||||
$em = $_.Exception.Message
|
||||
$msgBox = [System.Windows.Forms.MessageBox]::Show($em, "Information", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
|
||||
}
|
||||
}
|
1367
MainForm.psf
1367
MainForm.psf
File diff suppressed because it is too large
Load diff
1489
MainForm.psf.bak
1489
MainForm.psf.bak
File diff suppressed because it is too large
Load diff
1141
MainForm.psf.bak1
1141
MainForm.psf.bak1
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,3 @@
|
|||
# AudioSplitterV2
|
||||
|
||||
AudioSplitter rewrite in PowersHELL.
|
||||
|
||||
|
||||
# Info
|
||||
|
||||
This project is a work in progress, it is nowhere near completion and nothing works.
|
||||
AudioSplitter rewrite in PowersHELL.
|
|
@ -1,3 +0,0 @@
|
|||
@echo off
|
||||
PowerShell -ExecutionPolicy unrestricted -command "& { .\AudioSplitter2.ps1 }"
|
||||
pause
|
28
Startup.pss
28
Startup.pss
|
@ -1,28 +0,0 @@
|
|||
<File version="3.1">
|
||||
<Code><![CDATA[function Main
|
||||
{
|
||||
Param ([String]$Commandline)
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
#TODO: Add initialization script here (Load modules and check requirements)
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
if((Show-MainForm_psf) -eq 'OK')
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
$script:ExitCode = 0
|
||||
}]]></Code>
|
||||
<Assemblies>
|
||||
<Assembly>mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Assembly>
|
||||
<Assembly>System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Assembly>
|
||||
<Assembly>System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Assembly>
|
||||
<Assembly>System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Assembly>
|
||||
<Assembly>System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
|
||||
<Assembly>System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Assembly>
|
||||
<Assembly>System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Assembly>
|
||||
</Assemblies>
|
||||
</File>
|
|
@ -1,35 +0,0 @@
|
|||
<File version="2.1">
|
||||
<Code><![CDATA[function Main
|
||||
{
|
||||
Param ([String]$Commandline)
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
#TODO: Add initialization script here (Load modules and check requirements)
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
|
||||
if((Show-MainForm_psf) -eq 'OK')
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
$script:ExitCode = 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
]]></Code>
|
||||
<Assemblies>
|
||||
<Assembly>mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Assembly>
|
||||
<Assembly>System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Assembly>
|
||||
<Assembly>System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Assembly>
|
||||
<Assembly>System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Assembly>
|
||||
<Assembly>System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
|
||||
<Assembly>System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Assembly>
|
||||
<Assembly>System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</Assembly>
|
||||
</Assemblies>
|
||||
</File>
|
|
@ -1,3 +0,0 @@
|
|||
@echo off
|
||||
PowerShell -ExecutionPolicy unrestricted -command "& { . .\Globals.ps1; forceUpdate }"
|
||||
pause
|
BIN
bin/python32.dll
BIN
bin/python32.dll
Binary file not shown.
BIN
bin/vfr.exe
BIN
bin/vfr.exe
Binary file not shown.
18
bin/vfr/.gitignore
vendored
18
bin/vfr/.gitignore
vendored
|
@ -1,18 +0,0 @@
|
|||
*~
|
||||
*.pyc
|
||||
*.diff
|
||||
*.converted.txt
|
||||
*.mkv
|
||||
*.mka
|
||||
*.ass
|
||||
*.ffindex
|
||||
*.xml
|
||||
*.qpfile
|
||||
*.qpf
|
||||
Thumbs.db
|
||||
*.bak
|
||||
.project
|
||||
.pydevproject
|
||||
.settings
|
||||
test/result*
|
||||
test/chap*
|
|
@ -1,21 +0,0 @@
|
|||
The MIT License
|
||||
|
||||
Copyright (c) 2010 Ricardo Constantino
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -1,68 +0,0 @@
|
|||
vfr.py
|
||||
======
|
||||
|
||||
Inspired on: Daiz's AutoMKVChapters, TheFluff's split_aud, BD_Chapters
|
||||
|
||||
Needs: Python 3; MkvToolNix (for audio trimming)
|
||||
|
||||
What it does
|
||||
------------
|
||||
|
||||
* Reads the first line of uncommented Trims from an .avs;
|
||||
* Uses timecodes files to get each trim's frame's timestamp;
|
||||
* Offsets the trims accordingly;
|
||||
* Creates a basic xml with Matroska chapters, x264 chapters if ending in 'x264.txt' or OGM chapters if any other extension is used;
|
||||
* Creates a qpfile to use with x264;
|
||||
* Cuts and merges audio (as per split_aud, only using v2 timecodes instead of expecting cfr) (all options work as split_aud);
|
||||
* No longer needs tcConv but converts v1 timecodes to v2 internally;
|
||||
* If requested, can output v2 timecodes from v1 and fps parsing. If --ofps is being used, v2 timecodes will use it;
|
||||
* Can output a qpfile with converted frames meant to be used for an ivtc'd encode using non-ivtc'd frames (feature inspired by automkvchapters) (not completely accurate, obviously);
|
||||
* Using FFmpegsource's CorrectNTSCRationalFramerate, this is actually more precise in the v2 timecodes it produces than tcConv;
|
||||
* Accepts AutoMKVChapters-like templates.
|
||||
|
||||
Only the .avs with trims is required for vfr.py to run. You can use -v and/or --test to debug the script. All other options and arguments are optional.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
vfr.py -i audio.aac -o audio.cut.mka -f 30/1.001 -l tRim -c chapters.xml -t template.txt \
|
||||
-n chnames.txt -q qpfile.qpf -vmr --ofps 24/1.001 --timecodes v2.txt --test trims.avs outtrims.avs
|
||||
|
||||
Required:
|
||||
trims.avs = Gets first uncommented line starting with trims from this Avisynth script
|
||||
|
||||
Optional:
|
||||
-i = Audio to be cut (takes whatever mkvmerge takes)
|
||||
-o = Cut audio inside .mka
|
||||
Default: input.cut.mka
|
||||
-d = Manually set delay time for input audio (can be negative)
|
||||
-b = Reverse parsing of .avs (from bottom to top)
|
||||
-f = Frames per second or timecodes file if vfr input
|
||||
(takes "25", "24000/1001", "30000:1001", "24/1.001" and "30:1.001" as cfr input)
|
||||
Default: 30000/1001
|
||||
-l = Look for a line starting with a case-sensitive trim() or case-insensitive comment succeeding the trims, interpreted as a regular expression.
|
||||
Default: case insensitive trim
|
||||
-g = Specify directly the line used
|
||||
-c = Chapters file. If extension is 'xml', outputs MKV Chapters;
|
||||
if extension is 'x264.txt', outputs x264 Chapters; else, outputs OGM Chapters
|
||||
-n = Text file with chapter names, one per line; assumed to be UTF-8 without BOM
|
||||
-q = QPFile for use in x264; will use --ofps frames
|
||||
-t = Template file for advanced Matroska chapters
|
||||
-v = Verbose mode
|
||||
-m = Merge split audio files
|
||||
-r = Remove split audio files after merging
|
||||
--clip = Only pick trims that are using this clip name. Ex: ClipX.Trim(0,1) or Trim(ClipX,0,1)
|
||||
--uid = Set base UID for --template/--chnames
|
||||
--chnames = Path to basic text containing chapter titles separated by newlines
|
||||
--ofps = Output FPS (used in qpfile, v2 timecodes and avs export)
|
||||
Default: -f
|
||||
--timecodes = Output v2 timecodes (from fps and v1 parsing) (if using --ofps, outputs v2 timecodes using this)
|
||||
--sbr = Set this if inputting an .aac and it's SBR/HE-AAC
|
||||
--test = Test Mode (doesn't create new files)
|
||||
outtrims.avs = If chapparse.py is present, outputs .avs with offset and converted trims
|
||||
|
||||
To do:
|
||||
* Optimize code and/or improve its legibility
|
||||
|
||||
Known issues:
|
||||
* Conversion from a different input fps to output fps is not accurate (probably no way it can ever be fixed)
|
|
@ -1,263 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# chapparse.py
|
||||
import sys, re, getopt, os
|
||||
from string import Template
|
||||
|
||||
name = 'chapparse.py'
|
||||
version = '0.4'
|
||||
rat = re.compile('(\d+)(?:/|:)(\d+)')
|
||||
chapre = re.compile("CHAPTER\d+=(\S+)",re.I)
|
||||
x264 = 'x264-64'
|
||||
ffmpeg = 'ffmpeg'
|
||||
mkvmerge = 'mkvmerge'
|
||||
avs2yuv = 'avs2yuv'
|
||||
timeCodes = frameNumbers = merge = []
|
||||
|
||||
def main():
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "i:o:f:b:e:s:a:x:c:hmr",['help','avs=','test','x264opts='])
|
||||
except getopt.GetoptError as err:
|
||||
print(err)
|
||||
help()
|
||||
sys.exit()
|
||||
|
||||
set = dict(input='video.mkv',output='',audio='',index='',
|
||||
fps='24000/1001',batch='',method='x264',resize='',avs='',mergeFiles=False,removeFiles=False,
|
||||
x264opts='--preset placebo --crf 16 --level 41 --rc-lookahead 250',test=False,
|
||||
x264=x264,ffmpeg=ffmpeg,mkvmerge=mkvmerge,avs2yuv=avs2yuv,chapters='chapters.txt',crop='0,0,0,0')
|
||||
|
||||
for o, v in opts:
|
||||
if o == '-i':
|
||||
set['input'] = v
|
||||
elif o == '-o':
|
||||
set['output'] = v[:-4]
|
||||
elif o == '-f':
|
||||
set['fps'] = v
|
||||
elif o == '-b':
|
||||
set['batch'] = v
|
||||
elif o == '-e':
|
||||
set['method'] = v
|
||||
elif o == '-s':
|
||||
set['resize'] = v
|
||||
elif o == '-c':
|
||||
set['crop'] = v
|
||||
elif o in ('-x','--x264opts'):
|
||||
set['x264opts'] = v
|
||||
elif o == '-a':
|
||||
set['audio'] = v
|
||||
elif o in ('-h','--help'):
|
||||
help()
|
||||
sys.exit()
|
||||
elif o == '-m':
|
||||
set['mergeFiles'] = True
|
||||
elif o == '-r':
|
||||
set['removeFiles'] = True
|
||||
elif o == '--avs':
|
||||
set['avs'] = v
|
||||
elif o == '--test':
|
||||
set['test'] = True
|
||||
else:
|
||||
assert False, "unhandled option"
|
||||
|
||||
set['chapters'] = set['chapters'] if len(args) != 1 else args[0]
|
||||
|
||||
if set['output'] == '':
|
||||
set['output'] = set['input'][:-4]+'.encode'
|
||||
if set['avs'] == '' and set['method'] == 'avisynth':
|
||||
set['avs'] = set['output']+'.avs'
|
||||
if set['avs'] != '' and set['method'] == 'x264':
|
||||
set['method'] = 'avisynth'
|
||||
if set['batch'] == '':
|
||||
set['batch'] = set['output']+'.bat'
|
||||
if os.path.isfile(set['chapters']) != True:
|
||||
print("You must set a valid OGM chapters file.")
|
||||
sys.exit(2)
|
||||
|
||||
if set['test'] == True:
|
||||
for key in sorted(set):
|
||||
print(key.ljust(8),'=',set[key])
|
||||
print()
|
||||
|
||||
timeStrings = parseOgm(args[0])
|
||||
|
||||
timeCodes = [time2ms(timeString) for timeString in timeStrings]
|
||||
|
||||
frameNumbers = [ms2frame(timeCode,set['fps']) for timeCode in timeCodes]
|
||||
|
||||
set['cmd'] = Template('${piper}"${x264}" ${x264opts} --demuxer y4m${end} - -o "${output}-part${part}.mkv"')
|
||||
|
||||
if set['method'] == 'avisynth':
|
||||
set['avs'] = '"%s"' % set['avs']
|
||||
if set['test'] == False:
|
||||
set = writeAvisynth(set,frameNumbers)
|
||||
else:
|
||||
print('Writing avisynth script')
|
||||
elif set['method'] == 'ffmpeg':
|
||||
set['resize'] = ' -s '+set['resize'] if (set['method'] == 'ffmpeg' and set['resize'] != '') else ''
|
||||
elif set['method'] == 'x264':
|
||||
set['cmd'] = Template('"${x264}" ${x264opts}${seek}${end} $xinput -o "${output}-part${part}.mkv"')
|
||||
set['index'] = '"%s.x264.ffindex"' % set['input'] if set['input'][-3:] in ('mkv','mp4','wmv') else ''
|
||||
set['xinput'] = '"%s" --index %s' % (set['input'],set['index']) if set['index'] != '' else '"%s"' % set['input']
|
||||
x264crop = 'crop:'+set['crop'] if (set['method'] == 'x264' and set['crop'] != '0,0,0,0') else ''
|
||||
x264resize='resize:'+','.join(set['resize'].split('x')) if (set['method'] == 'x264' and set['resize'] != '') else ''
|
||||
sep = '/' if (x264crop != '' and x264resize != '') else ''
|
||||
set['x264opts'] = set['x264opts']+' --vf %s%s%s' % (x264crop,sep,x264resize) if (x264crop != '' or x264resize != '') else set['x264opts']
|
||||
|
||||
writeBatch(set,frameNumbers,timeStrings)
|
||||
|
||||
def help():
|
||||
print("""
|
||||
%s %s
|
||||
Usage: chapparse.py [options] chapters.txt
|
||||
chapters.txt is an OGM chapters file to get chapter points from whence to
|
||||
separate the encodes
|
||||
|
||||
Options:
|
||||
-i video.mkv
|
||||
Video to be encoded
|
||||
-o encode.mkv
|
||||
Encoded video
|
||||
-f 24000/1001
|
||||
Frames per second
|
||||
-s 1280x720
|
||||
Resolution to resize to (no default)
|
||||
-e x264
|
||||
Method of resizing [avisynth,ffmpeg,x264]
|
||||
-a audio.m4a
|
||||
Audio to mux in the final file
|
||||
-b encode.bat
|
||||
Batch file with the instructions for chapter-separated encode
|
||||
-x "--preset placebo --crf 16 --level 41 --rc-lookahead 250", --x264opts
|
||||
x264 options (don't use --demuxer, --input, --output or --frames)
|
||||
--avs encode.avs
|
||||
If using avisynth method
|
||||
-m
|
||||
Merge parts
|
||||
-r
|
||||
Remove extra files
|
||||
-h, --help
|
||||
This help file""" % (name,version))
|
||||
|
||||
def time2ms(ts):
|
||||
|
||||
t = ts.split(':')
|
||||
h = int(t[0]) * 3600000
|
||||
m = h + int(t[1]) * 60000
|
||||
ms = round(m + float(t[2]) * 1000)
|
||||
|
||||
return ms
|
||||
|
||||
def ms2frame(ms,fps):
|
||||
|
||||
s = ms / 1000
|
||||
fps = rat.search(fps).groups() if rat.search(fps) else \
|
||||
[re.search('(\d+)',fps).group(0),'1']
|
||||
frame = round((int(fps[0])/int(fps[1])) * s)
|
||||
|
||||
return frame
|
||||
|
||||
def parseOgm(file):
|
||||
|
||||
timeStrings = []
|
||||
|
||||
with open(file) as chapFile:
|
||||
for line in chapFile:
|
||||
timeString = chapre.match(line)
|
||||
if timeString != None:
|
||||
timeStrings.append(timeString.group(1))
|
||||
|
||||
return timeStrings
|
||||
|
||||
def writeAvisynth(set,frameNumbers):
|
||||
# needs dict with 'avs', 'input', 'resize' (if needed) and list with frameNumbers
|
||||
if os.path.isfile(set['avs'][1:-1]) != True:
|
||||
with open(set['avs'][1:-1],'w') as avs:
|
||||
if set['input'][:-4] in ('.mkv','.wmv','.mp4'):
|
||||
avs.write('FFVideoSource("%s")\n' % set['input'])
|
||||
elif set['input'][:-4] == '.avi':
|
||||
avs.write('AviSource("%s")\n' % set['input'])
|
||||
elif set['input'] != '':
|
||||
avs.write('DirectShowSource("%s")\n' % set['input'])
|
||||
if set['resize'] != '':
|
||||
avs.write('Spline36Resize(%s)\n' % ','.join(set['resize'].split('x')))
|
||||
avs.write('+'.join(['Trim(%d,%d)' % (frameNumbers[i],frameNumbers[i+1]-1) for i in range(len(frameNumbers)-1)]))
|
||||
avs.write('+Trim(%d,0)\n' % frameNumbers[-1])
|
||||
else:
|
||||
with open(set['avs'][1:-1],'a') as avs:
|
||||
avs.write('\n')
|
||||
avs.write('+'.join(['Trim(%d,%d)' % (frameNumbers[i],frameNumbers[i+1]-1) for i in range(len(frameNumbers)-1)]))
|
||||
avs.write('+Trim(%d,0)\n' % frameNumbers[-1])
|
||||
|
||||
set['resize'] = ''
|
||||
if set['input'][:-3] in ('mkv','wmv','mp4'):
|
||||
set['index'] = '"%s.mkv.ffindex"' % set['output']
|
||||
|
||||
return set
|
||||
|
||||
def cmdMake(set,frameNumbers,timeStrings,i):
|
||||
begin = frameNumbers[i]
|
||||
frames = frameNumbers[i+1]-begin if i != len(frameNumbers)-1 else 0
|
||||
|
||||
if set['method'] == 'avisynth':
|
||||
set['seek'] = ' -seek %d' % begin
|
||||
elif set['method'] == 'ffmpeg':
|
||||
set['seek'] = ' -ss %s' % timeStrings[i]
|
||||
elif set['method'] == 'x264':
|
||||
set['seek'] = ' --seek %d' % begin
|
||||
if frames != 0:
|
||||
if set['method'] == 'avisynth':
|
||||
set['frames'] = ' -frames %d' % frames
|
||||
elif set['method'] == 'ffmpeg':
|
||||
set['frames'] = ' -vframes %d' % frames
|
||||
elif set['method'] == 'x264':
|
||||
set['frames'] = ''
|
||||
set['end'] = ' --frames %d' % frames
|
||||
else:
|
||||
set['end'] = set['frames'] = ''
|
||||
|
||||
set['merge'] = '"%s-part%d.mkv"' % (set['output'],i+1)
|
||||
|
||||
set['part'] = i+1
|
||||
|
||||
if set['method'] == 'avisynth':
|
||||
set['piper'] = Template('"${avs2yuv}"${seek}${frames} $avs -o - | ')
|
||||
elif set['method'] == 'ffmpeg':
|
||||
set['piper'] = Template('"${ffmpeg}" -i "${input}"${resize}${seek}${frames} -f yuv4mpegpipe -sws_fags spline - | ')
|
||||
|
||||
if set['method'] in ('avisynth','ffmpeg'):
|
||||
set['piper'] = set['piper'].substitute(set)
|
||||
|
||||
return set
|
||||
|
||||
def writeBatch(set,frameNumbers,timeStrings):
|
||||
if set['test'] == False:
|
||||
with open(set['batch'],'w') as batch:
|
||||
merge = []
|
||||
if os.name == 'posix':
|
||||
batch.write('#!/bin/sh\n\n')
|
||||
for i in range(len(frameNumbers)):
|
||||
set2 = cmdMake(set,frameNumbers,timeStrings,i)
|
||||
batch.write(set['cmd'].substitute(set2)+'\n')
|
||||
merge.append(set2['merge'])
|
||||
|
||||
if set['mergeFiles'] == True:
|
||||
batch.write('\n"%s" -o "%s" %s --default-duration "1:%sfps"' % (set['mkvmerge'],set['output']+'.mkv',' +'.join(merge),set['fps']))
|
||||
if set['audio'] != '':
|
||||
batch.write(' -D --no-chapters "%s"' % set['audio'])
|
||||
batch.write(' --chapters "%s"' % set['chapters'])
|
||||
batch.write('\n')
|
||||
rem = ' '.join(merge)
|
||||
if set['removeFiles'] == True and os.name == 'nt':
|
||||
batch.write('del %s' % rem)
|
||||
elif set['removeFiles'] == True and os.name == 'posix':
|
||||
batch.write('rm %s' % rem)
|
||||
else:
|
||||
print('Writing batch file')
|
||||
#print('Example:',set['cmd'].format(cmdMake(set,frameNumbers,timeStrings,3)))
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
main()
|
||||
else:
|
||||
print('Usage: chapparse.py [options] chapters.txt')
|
||||
sys.exit()
|
|
@ -1,16 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from sys import argv
|
||||
try:
|
||||
from vfr import parse_tc
|
||||
except ImportError:
|
||||
exit("tcconv requires vfr.py in order to work")
|
||||
|
||||
if len(argv) >= 4:
|
||||
fps = argv[1]
|
||||
tc = argv[2]
|
||||
frames = int(argv[3])
|
||||
first = int(argv[4]) if len(argv) == 5 else 0
|
||||
parse_tc(fps, frames, tc, first)
|
||||
else:
|
||||
exit("tcconv.py <fps/v1 timecodes> <output v2 timecodes> <frames> [<first>]")
|
|
@ -1,353 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from io import open
|
||||
|
||||
class AutoMKVChapters:
|
||||
class Template:
|
||||
def __init__(self):
|
||||
from random import randint
|
||||
self.uid = randint(10**4,10**6)
|
||||
self.num_editions = 1
|
||||
self.lang = ['eng']
|
||||
self.country = ['us']
|
||||
self.fps = '30'
|
||||
self.ofps = '24'
|
||||
self.qpf = '0'
|
||||
self.idr = False
|
||||
self.trims = None
|
||||
self.kframes = None
|
||||
|
||||
def toxml(self,chapfile):
|
||||
|
||||
chf = open(chapfile+'.xml','w',encoding='utf-8')
|
||||
head = '<?xml version="1.0" encoding="UTF-8"?>\n<!-- <!DOCTYPE Tags SYSTEM "matroskatags.dtd"> -->\n'
|
||||
chf.write(head+'<Chapters>\n')
|
||||
|
||||
if self.num_editions > 1:
|
||||
tagf = open(chapfile+'tags.xml','w')
|
||||
tagf.write(head+'<Tags>\n')
|
||||
else:
|
||||
tagf = False
|
||||
|
||||
for ed in self.editions:
|
||||
chf.write('\t<EditionEntry>\n')
|
||||
chf.write('\t\t<EditionFlagHidden>{0:d}</EditionFlagHidden>\n'.format(ed.hidden) if ed.hidden else '')
|
||||
chf.write('\t\t<EditionFlagDefault>{0:d}</EditionFlagDefault>\n'.format(ed.default) if ed.default else '')
|
||||
chf.write('\t\t<EditionFlagOrdered>{0:d}</EditionFlagOrdered>\n'.format(ed.ordered) if ed.ordered else '')
|
||||
chf.write('\t\t<EditionUID>{0:d}</EditionUID>\n'.format(ed.uid))
|
||||
|
||||
if tagf:
|
||||
tagf.write('\t<Tag>\n\t\t<Targets>\n')
|
||||
tagf.write('\t\t\t<EditionUID>{0:d}</EditionUID>\n'.format(ed.uid))
|
||||
tagf.write('\t\t\t<TargetTypeValue>50</TargetTypeValue>\n\t\t</Targets>\n')
|
||||
num_names = len(ed.name) if len(ed.name) < len(self.lang) else len(self.lang)
|
||||
for i in range(num_names):
|
||||
tagf.write('\t\t<Simple>\n\t\t\t<Name>TITLE</Name>\n')
|
||||
tagf.write('\t\t\t<String>{0}</String>\n'.format(ed.name[i] if ed.name[i] != '' else ed.name[i-1]))
|
||||
tagf.write('\t\t\t<TagLanguage>{0}</TagLanguage>\n'.format(self.lang[i]))
|
||||
tagf.write('\t\t\t<DefaultLanguage>{0:d}</DefaultLanguage>\n'.format(1 if i == 0 else 0))
|
||||
tagf.write('\t\t</Simple>\n')
|
||||
tagf.write('\t</Tag>\n')
|
||||
|
||||
for ch in ed.chapters:
|
||||
chf.write('\t\t<ChapterAtom>\n')
|
||||
num_names = len(ch.name) if len(ch.name) < len(self.lang) else len(self.lang)
|
||||
for i in range(num_names):
|
||||
chf.write('\t\t\t<ChapterDisplay>\n')
|
||||
chf.write('\t\t\t\t<ChapterString>{0}</ChapterString>\n'.format(ch.name[i] if ch.name[i] != '' else ch.name[i-1]))
|
||||
chf.write('\t\t\t\t<ChapterLanguage>{0}</ChapterLanguage>\n'.format(self.lang[i]) if self.lang[i] != 'eng' else '')
|
||||
chf.write('\t\t\t\t<ChapterCountry>{0}</ChapterCountry>\n'.format(self.country[i]) if i < len(self.country) else '')
|
||||
chf.write('\t\t\t</ChapterDisplay>\n')
|
||||
chf.write('\t\t\t<ChapterUID>{0:d}</ChapterUID>\n'.format(ch.uid))
|
||||
chf.write('\t\t\t<ChapterTimeStart>{0}</ChapterTimeStart>\n'.format(ch.start))
|
||||
chf.write('\t\t\t<ChapterTimeEnd>{0}</ChapterTimeEnd>\n'.format(ch.end) if ch.end else '')
|
||||
chf.write('\t\t\t<ChapterFlagHidden>{0:d}</ChapterFlagHidden>\n'.format(ch.hidden) if ch.hidden != 0 else '')
|
||||
chf.write('\t\t\t<ChapterFlagEnabled>{0:d}</ChapterFlagEnabled>\n'.format(ch.enabled) if ch.enabled != 1 else '')
|
||||
chf.write('\t\t\t<ChapterSegmentUID format="hex">{0}</ChapterSegmentUID>\n'.format(ch.suid) if ch.suid else '')
|
||||
chf.write('\t\t</ChapterAtom>\n')
|
||||
|
||||
chf.write('\t</EditionEntry>\n')
|
||||
|
||||
chf.write('</Chapters>\n')
|
||||
|
||||
if tagf:
|
||||
tagf.write('</Tags>\n')
|
||||
tagf.close()
|
||||
|
||||
if self.qpf != '0' and self.kframes:
|
||||
from vfr import write_qpfile
|
||||
if self.qpf != '1':
|
||||
qpfile = self.qpf
|
||||
else:
|
||||
qpfile = chapfile+'.qpfile'
|
||||
write_qpfile(qpfile, self.kframes, self.idr)
|
||||
|
||||
def connect_with_vfr(self,avs,label=None,clip=None):
|
||||
"""
|
||||
Connects templates.py with vfr.py, enabling its use outside of vfr.py.
|
||||
|
||||
Uses the same quirks as AMkvC but only for 24 and 30 fps.
|
||||
Ex: inputfps=30 is understood as being '30*1000/1001'
|
||||
|
||||
"""
|
||||
|
||||
from vfr import parse_trims, fmt_time
|
||||
|
||||
# compensate for amkvc's fps assumption
|
||||
if self.fps in ('24','30'):
|
||||
fps = self.fps + '/1.001'
|
||||
else:
|
||||
fps = str(self.fps)
|
||||
if self.ofps and self.ofps in ('24','30'):
|
||||
ofps = self.ofps + '/1.001'
|
||||
else:
|
||||
ofps = str(self.ofps)
|
||||
|
||||
Trims2, Trims2ts = parse_trims(avs, fps, ofps, label=label, clip=clip)[2:4]
|
||||
Trims2ts = [(fmt_time(i[0]),fmt_time(i[1]) if i[1] != 0 else None) for i in Trims2ts]
|
||||
|
||||
self.trims = Trims2ts
|
||||
self.kframes = Trims2
|
||||
|
||||
def parse_mkv(self, path):
|
||||
"""Parse a Matroska file for SegmentUID and Duration"""
|
||||
import binascii
|
||||
import struct
|
||||
|
||||
def get_data_len(byte):
|
||||
"""Get the length (bytes) of the element data"""
|
||||
n = ord(byte)
|
||||
mask = 0b10000000
|
||||
while not n & mask:
|
||||
mask >>= 1
|
||||
return n & ~mask
|
||||
|
||||
suid = tcscale = duration = 0
|
||||
with open(path, 'rb') as file:
|
||||
if file.read(4) != b'\x1A\x45\xDF\xA3': # not a Matroska file
|
||||
return suid, duration
|
||||
chunk_size = 100000 # 100 kB
|
||||
i = 0
|
||||
while True:
|
||||
if suid and tcscale and duration:
|
||||
break
|
||||
bin = file.read(chunk_size)
|
||||
if not bin:
|
||||
break
|
||||
suid_pos = bin.find(b'\x73\xA4\x90') # \x90 -> 16 bytes
|
||||
if suid_pos != -1:
|
||||
suid_pos = 4 + i * chunk_size + suid_pos + 3
|
||||
file.seek(suid_pos)
|
||||
suid = binascii.hexlify(file.read(16)).decode()
|
||||
tcscale_pos = bin.find(b'\x2A\xD7\xB1')
|
||||
if tcscale_pos != -1:
|
||||
tcscale_pos = 4 + i * chunk_size + tcscale_pos + 3
|
||||
file.seek(tcscale_pos)
|
||||
tcscale_len = get_data_len(file.read(1))
|
||||
tcscale = int(binascii.hexlify(file.read(tcscale_len)), 16)
|
||||
duration_pos = bin.find(b'\x44\x89\x84') # float (4 bytes)
|
||||
if duration_pos != -1:
|
||||
duration_pos = 4 + i * chunk_size + duration_pos + 3
|
||||
file.seek(duration_pos)
|
||||
duration = struct.unpack('>f', file.read(4))[0]
|
||||
if not duration: # double (8 bytes)
|
||||
duration_pos = bin.find(b'\x44\x89\x88')
|
||||
if duration_pos != -1:
|
||||
duration_pos = 4 + i * chunk_size + duration_pos + 3
|
||||
file.seek(duration_pos)
|
||||
duration = struct.unpack('>d', file.read(8))[0]
|
||||
if bin.find(b'\x1F\x43\xB6\x75') != -1:
|
||||
# segment info should be before the clusters
|
||||
break
|
||||
i += 1
|
||||
duration = duration * tcscale / 1000000
|
||||
return suid, duration
|
||||
|
||||
class Edition:
|
||||
def __init__(self):
|
||||
self.default = 0
|
||||
self.name = ['Default']
|
||||
self.hidden = 0
|
||||
self.ordered = 0
|
||||
self.num_chapters = 1
|
||||
self.uid = 0
|
||||
|
||||
class Chapter:
|
||||
def __init__(self):
|
||||
self.name = ['Chapter']
|
||||
self.chapter = False
|
||||
self.start = False
|
||||
self.end = False
|
||||
self.suid = False
|
||||
self.hidden = 0
|
||||
self.uid = 0
|
||||
self.enabled = 1
|
||||
|
||||
def __init__(self, templatefile, output=None, avs=None, trims=None,
|
||||
kframes=None, uid=None, label=None, ifps=None, clip=None,
|
||||
idr=False):
|
||||
try:
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser as configparser
|
||||
from io import open
|
||||
|
||||
# Init config
|
||||
config = configparser.ConfigParser()
|
||||
template = open(templatefile, encoding='utf-8')
|
||||
|
||||
# Read template
|
||||
config.readfp(template)
|
||||
template.close()
|
||||
|
||||
# Template defaults
|
||||
self = self.Template()
|
||||
self.editions = []
|
||||
self.uid = uid if uid else self.uid
|
||||
|
||||
# Set mkvinfo path
|
||||
from vfr import mkvmerge, parse_with_mkvmerge, fmt_time
|
||||
from os.path import dirname, join, isfile
|
||||
|
||||
# Set placeholder for mkvinfo output
|
||||
mkv_globbed = False
|
||||
mkvinfo = {}
|
||||
|
||||
for k, v in config.items('info'):
|
||||
if k == 'lang':
|
||||
self.lang = v.split(',')
|
||||
elif k == 'country':
|
||||
self.country = v.split(',')
|
||||
elif k == 'inputfps':
|
||||
self.fps = v
|
||||
elif k == 'outputfps':
|
||||
self.ofps = v
|
||||
elif k == 'createqpfile':
|
||||
self.qpf = v
|
||||
elif k == 'uid':
|
||||
self.uid = int(v)
|
||||
elif k == 'editions':
|
||||
self.num_editions = int(v)
|
||||
|
||||
if avs and not ifps:
|
||||
self.connect_with_vfr(avs, label, clip)
|
||||
elif trims:
|
||||
self.trims = trims
|
||||
self.kframes = kframes
|
||||
else:
|
||||
self.trims = False
|
||||
self.idr = idr
|
||||
|
||||
for i in range(self.num_editions):
|
||||
from re import compile
|
||||
ed = self.Edition()
|
||||
ed.uid = self.uid * 100
|
||||
self.uid += 1
|
||||
cuid = ed.uid
|
||||
ed.num = i+1
|
||||
ed.chapters = []
|
||||
stuff = {}
|
||||
|
||||
for k, v in config.items('edition{0:d}'.format(ed.num)):
|
||||
if k == 'default':
|
||||
ed.default = int(v)
|
||||
elif k == 'name':
|
||||
ed.name = v.split(',')
|
||||
elif k == 'ordered':
|
||||
ed.ordered = int(v)
|
||||
elif k == 'hidden':
|
||||
ed.hidden = int(v)
|
||||
elif k == 'chapters':
|
||||
ed.num_chapters = int(v)
|
||||
for i in range(ed.num_chapters):
|
||||
stuff[i+1] = []
|
||||
elif k == 'uid':
|
||||
ed.uid = int(v)
|
||||
else:
|
||||
opt_re = compile('(\d+)(\w+)')
|
||||
ret = opt_re.search(k)
|
||||
if ret:
|
||||
stuff[int(ret.group(1))].append((ret.group(2),v))
|
||||
|
||||
for j in range(ed.num_chapters):
|
||||
ch = self.Chapter()
|
||||
cuid += 1
|
||||
ch.uid = cuid
|
||||
ch.num = j+1
|
||||
|
||||
for k, v in stuff[j+1]:
|
||||
if k == 'name':
|
||||
ch.name = v.split(',')
|
||||
elif k == 'chapter':
|
||||
ch.chapter = int(v)
|
||||
elif k == 'start':
|
||||
ch.start = v
|
||||
elif k == 'end':
|
||||
ch.end = v
|
||||
elif k == 'suid':
|
||||
ch.suid = v.strip() if ret else 0
|
||||
elif k == 'hidden':
|
||||
ch.hidden = int(v)
|
||||
elif k == 'enabled':
|
||||
ch.enabled = int(v)
|
||||
|
||||
if ch.suid and not isfile(ch.suid):
|
||||
ch.suid = ch.suid.replace('0x','').lower().replace(' ','')
|
||||
|
||||
if ch.chapter and not (ch.start and ch.end):
|
||||
ch.start, ch.end = self.trims[ch.chapter-1] if self.trims else (ch.start, ch.end)
|
||||
elif ch.suid:
|
||||
mkvfiles = []
|
||||
if isfile(ch.suid):
|
||||
mkvfiles = [ch.suid]
|
||||
elif not mkv_globbed:
|
||||
from glob import glob
|
||||
mkvfiles = glob('*.mkv') + glob(join(dirname(avs),'*.mkv'))
|
||||
mkv_globbed = True
|
||||
if mkvfiles:
|
||||
if parse_with_mkvmerge:
|
||||
from subprocess import check_output
|
||||
import json
|
||||
for file in mkvfiles:
|
||||
info = check_output([mkvmerge, '-i', '-F', 'json',
|
||||
'--output-charset', 'utf-8', file]).decode('utf-8')
|
||||
try:
|
||||
props = json.loads(info).get("container", {}).get("properties", {})
|
||||
ch.suid = props.get("segment_uid", 0)
|
||||
duration = props.get("duration", 0)
|
||||
mkvinfo[ch.suid] = {'file': file,
|
||||
'duration': fmt_time(duration * 10**6)
|
||||
if duration else 0}
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
for file in mkvfiles:
|
||||
ch.suid, duration = self.parse_mkv(file)
|
||||
mkvinfo[ch.suid] = {'file': file,
|
||||
'duration': fmt_time(duration * 10**6)
|
||||
if duration else 0}
|
||||
if not (ch.start or ch.end):
|
||||
ch.start = fmt_time(0) if not ch.start else ch.start
|
||||
ch.end = mkvinfo[ch.suid]['duration'] if not ch.end and (ch.suid in mkvinfo) else ch.end
|
||||
|
||||
ed.chapters.append(ch)
|
||||
self.editions.append(ed)
|
||||
if output:
|
||||
self.toxml(output)
|
||||
|
||||
|
||||
def main(args):
|
||||
|
||||
template = args[0]
|
||||
output = args[1]
|
||||
avs = args[2] if len(args) == 3 else None
|
||||
|
||||
chaps = AutoMKVChapters(template,output,avs)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from sys import argv, exit
|
||||
if len(argv) > 1:
|
||||
main(argv[1:])
|
||||
else:
|
||||
exit("templates.py <template file> <output filenames w/o extension> [<avisynth file>]")
|
728
bin/vfr/vfr.py
728
bin/vfr/vfr.py
|
@ -1,728 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from sys import exit, argv
|
||||
from re import compile
|
||||
from os.path import isfile, splitext
|
||||
from math import floor, ceil
|
||||
from fractions import Fraction
|
||||
from io import open
|
||||
|
||||
exts = {
|
||||
"xml": "MKV",
|
||||
"x264.txt": "X264"
|
||||
}
|
||||
default_fps = "30000/1001"
|
||||
|
||||
# Change the paths here if the programs aren't in your $PATH
|
||||
mkvmerge = r'mkvmerge'
|
||||
|
||||
# Check to utilize mkvtoolnix for obtaining the uid and duration of the mkv
|
||||
# files specified on templates, instead of letting this script parse them
|
||||
# directly (faster). Just in case the later fails.
|
||||
parse_with_mkvmerge = False
|
||||
|
||||
def main(args):
|
||||
from optparse import OptionParser
|
||||
p = OptionParser(description='Grabs avisynth trims and outputs chapter '
|
||||
'file, qpfile and/or cuts audio (works with cfr and '
|
||||
'vfr input)',
|
||||
version='VFR Chapter Creator 0.10.0',
|
||||
usage='%prog [options] infile.avs [outfile.avs]')
|
||||
p.add_option('--label', '-l', action="store", dest="label",
|
||||
help="Look for a trim() statement or succeeding comment only "
|
||||
"on lines matching LABEL. Default: case insensitive trim")
|
||||
p.add_option('--clip', action="store", dest="clip",
|
||||
help="Look for trims() using specific clip, like"
|
||||
"Trim(ClipX,0,100). Default: any trim")
|
||||
p.add_option('--line', '-g', action="store", type="int", dest="line",
|
||||
help="Specify directly the line used")
|
||||
p.add_option('--input', '-i', action="store", help='Audio file to be cut',
|
||||
dest="input")
|
||||
p.add_option('--output', '-o', action="store",
|
||||
help='Cut audio from MKVMerge', dest="output")
|
||||
p.add_option('--fps', '-f', action="store",
|
||||
help='Frames per second or Timecodes file', dest="fps")
|
||||
p.add_option('--ofps', action="store", help='Output frames per second',
|
||||
dest="ofps")
|
||||
p.add_option('--timecodes', action="store", help='Output v2 timecodes',
|
||||
dest="otc")
|
||||
p.add_option('--chapters', '-c', action="store",
|
||||
help='Chapters file [.{0}/.txt]'.format("/.".join(
|
||||
exts.keys())), dest="chapters")
|
||||
p.add_option('--chnames', '-n', action="store",
|
||||
help='Path to template file for chapter names (utf8 w/o bom)',
|
||||
dest="chnames")
|
||||
p.add_option('--template', '-t', action="store",
|
||||
help="Template file for chapters", dest="template")
|
||||
p.add_option('--uid', action="store",
|
||||
help="Base UID for --template or --chnames", dest="uid")
|
||||
p.add_option('--qpfile', '-q', action="store", help='QPFile for x264',
|
||||
dest="qpfile")
|
||||
p.add_option('--verbose', '-v', action="store_true", help='Verbose',
|
||||
dest="verbose")
|
||||
p.add_option('--merge', '-m', action="store_true", help='Merge cut files',
|
||||
dest="merge")
|
||||
p.add_option('--remove', '-r', action="store_true",
|
||||
help='Remove cut files', dest="remove")
|
||||
p.add_option('--delay', '-d', action="store",
|
||||
help="Set delay of audio (can be negative)", dest="delay")
|
||||
p.add_option('--reverse', '-b', action="store_true",
|
||||
help="Reverse parsing of .avs", dest="reverse")
|
||||
p.add_option('--test', action="store_true",
|
||||
help="Test mode (do not create new files)", dest="test")
|
||||
p.add_option('--IDR', '--idr', action="store_true",
|
||||
help="Set this to make qpfile with IDR frames instead of K frames",
|
||||
dest="IDR")
|
||||
p.add_option('--sbr', action="store_true",
|
||||
help="Set this if inputting an .aac and it's SBR/HE-AAC",
|
||||
dest="sbr")
|
||||
(o, a) = p.parse_args(args)
|
||||
|
||||
if len(a) < 1:
|
||||
p.error("No avisynth script specified.")
|
||||
if not o.fps:
|
||||
o.fps = default_fps
|
||||
ifps = False
|
||||
else:
|
||||
ifps = True
|
||||
|
||||
#Determine chapter type
|
||||
if o.chapters:
|
||||
chre = compile("\.({0})$(?i)".format("|".join(exts.keys())))
|
||||
ret = chre.search(o.chapters)
|
||||
chapter_type = exts[ret.group(1).lower()] if ret else "OGM"
|
||||
else:
|
||||
chapter_type = ''
|
||||
|
||||
if o.template and o.chnames:
|
||||
p.error("Choose either --chnames or --template, not both.")
|
||||
elif o.template and chapter_type != 'MKV':
|
||||
p.error("--template needs to output to .xml.")
|
||||
|
||||
if not o.output and o.input:
|
||||
ret = splitext(o.input)
|
||||
o.output = '{0}.cut.mka'.format(ret[0])
|
||||
|
||||
if o.verbose:
|
||||
status = "Avisynth file: \t{0}\n".format(a[0])
|
||||
status += "Label: \t\t{0}\n".format(o.label) if o.label else ""
|
||||
status += "Clip name: \t{0}\n".format(o.clip) if o.clip else ""
|
||||
status += ("Parsing order: \t{0}\n".format("Bottom to top" if
|
||||
o.reverse else "Top to bottom"))
|
||||
status += "Line: \t\t{0}\n".format(o.line) if o.line else ""
|
||||
status += ("Audio file: \t{0}{1}\n".format(o.input, "(SBR)" if o.sbr
|
||||
else "") if o.input else "")
|
||||
status += "Cut Audio file: {0}\n".format(o.output) if o.output else ""
|
||||
status += "Timecodes/FPS: \t{0}{1}\n".format(o.fps, " to " + o.ofps if
|
||||
o.ofps else "") if o.ofps != o.fps else ""
|
||||
status += "Output v2 Tc: \t{0}\n".format(o.otc) if o.otc else ""
|
||||
status += ("Chapters file: \t{0}{1}\n".format(o.chapters,
|
||||
" ({0})".format(chapter_type) if chapter_type else "") if
|
||||
o.chapters else "")
|
||||
status += ("Chapter Names: \t{0}\n".format(o.chnames) if o.chnames
|
||||
else "")
|
||||
status += ("Template file: \t{0}\n".format(o.template) if o.template
|
||||
else "")
|
||||
status += "QP file: \t{0} ({1} frames)\n".format(o.qpfile, 'I' if
|
||||
o.IDR else 'K') if o.qpfile else ""
|
||||
status += "\n"
|
||||
status += ("Merge/Rem files:{0}/{1}\n".format(o.merge, o.remove) if
|
||||
o.merge or o.remove else "")
|
||||
status += ("Verbose: \t{0}\n".format(o.verbose) if o.verbose
|
||||
else "")
|
||||
status += "Test Mode: \t{0}\n".format(o.test) if o.test else ""
|
||||
|
||||
print(status)
|
||||
|
||||
# Get frame numbers and corresponding timecodes from avs
|
||||
Trims, Trimsts, Trims2, Trims2ts, audio = parse_trims(a[0], o.fps, o.ofps,
|
||||
o.otc if not o.test else '', o.input,
|
||||
o.label, o.reverse, o.line, o.clip,
|
||||
o.merge)
|
||||
|
||||
nt2 = len(Trims2ts)
|
||||
if o.verbose:
|
||||
print('In trims: {0}\n'.format(', '.join(['({0},{1})'.format(i[0],
|
||||
i[1]) for i in Trims])))
|
||||
print('In timecodes: {0}\n'.format(', '.join(['({0},{1})'.format(i[0],
|
||||
i[1]) for i in Trimsts])))
|
||||
print('Out trims: {0}\n'.format(', '.join(['({0},{1})'.format(i[0],
|
||||
i[1]) for i in Trims2])))
|
||||
print('Out timecodes: {0}\n'.format(', '.join(['({0},{1})'.format(
|
||||
fmt_time(i[0]), fmt_time(i[1])) for i in Trims2ts])))
|
||||
|
||||
# make qpfile
|
||||
if o.qpfile and not o.template:
|
||||
if not o.test:
|
||||
write_qpfile(o.qpfile, Trims2, o.IDR)
|
||||
if o.verbose:
|
||||
print('Writing keyframes to {0}\n'.format(o.qpfile))
|
||||
|
||||
# make audio cuts
|
||||
if o.input:
|
||||
split_audio(audio, o.input, o.output, o.delay, o.sbr, o.merge, o.remove,
|
||||
o.verbose, o.test)
|
||||
|
||||
# make offseted avs
|
||||
if len(a) > 1:
|
||||
try:
|
||||
from chapparse import writeAvisynth
|
||||
fNum = [i[0] for i in Trims2]
|
||||
set = {'avs': '"' + a[1] + '"', 'input': '', 'resize': ''}
|
||||
writeAvisynth(set, fNum)
|
||||
except ImportError:
|
||||
print('Script chapparse.py needed for avisynth output to work.')
|
||||
|
||||
# write chapters
|
||||
if chapter_type:
|
||||
|
||||
if chapter_type == 'MKV':
|
||||
Trims2ts = [(fmt_time(i[0]), fmt_time(i[1]) if i[1] != 0 else None)
|
||||
for i in Trims2ts]
|
||||
|
||||
if o.template:
|
||||
from templates import AutoMKVChapters as amkvc
|
||||
output = o.chapters[:-4] if not o.test else None
|
||||
chaps = amkvc(o.template, output=output, avs=a[0], trims=Trims2ts,
|
||||
kframes=Trims2, uid=o.uid, label=o.label,
|
||||
ifps=ifps, clip=o.clip, idr=o.IDR)
|
||||
|
||||
else:
|
||||
# Assign names to each chapter if --chnames
|
||||
chapter_names = []
|
||||
|
||||
if o.chnames:
|
||||
with open(o.chnames, encoding='utf-8') as f:
|
||||
[chapter_names.append(line.strip()) for line in
|
||||
f.readlines()]
|
||||
|
||||
if not o.chnames or len(chapter_names) < len(Trims2ts):
|
||||
# The if statement is for clarity; it doesn't actually do
|
||||
# anything useful
|
||||
for i in range(len(chapter_names), len(Trims2ts)):
|
||||
chapter_names.append("Chapter {:02d}".format(i + 1))
|
||||
|
||||
if chapter_type == 'MKV':
|
||||
from templates import AutoMKVChapters as amkvc
|
||||
tmp = amkvc.Template()
|
||||
tmp.trims = Trims2ts
|
||||
tmp.kframes = Trims2
|
||||
if o.qpfile:
|
||||
tmp.qpf = o.qpfile
|
||||
tmp.idr = o.IDR
|
||||
ed = tmp.Edition()
|
||||
ed.default = 1
|
||||
ed.num_chapters = len(chapter_names)
|
||||
ed.uid = int(o.uid) * 100 if o.uid else tmp.uid * 100
|
||||
cuid = ed.uid
|
||||
ed.chapters = []
|
||||
for i in range(len(chapter_names)):
|
||||
ch = tmp.Chapter()
|
||||
cuid += 1
|
||||
ch.uid = cuid
|
||||
ch.name = [chapter_names[i]]
|
||||
ch.start, ch.end = (Trims2ts[i][0], Trims2ts[i][1])
|
||||
ed.chapters.append(ch)
|
||||
tmp.editions = [ed]
|
||||
chaps = tmp
|
||||
|
||||
if not o.test:
|
||||
if chapter_type == 'MKV':
|
||||
chaps.toxml(o.chapters[:-4])
|
||||
else:
|
||||
with open(o.chapters, "w", encoding='utf-8') as output:
|
||||
if chapter_type == 'OGM':
|
||||
chap = ('CHAPTER{1:02d}={0}\nCHAPTER{1:02d}'
|
||||
'NAME={2}\n')
|
||||
elif chapter_type == 'X264':
|
||||
chap = '{0} {2}\n'
|
||||
Trims2ts = [fmt_time(i[0], 1) for i in Trims2ts]
|
||||
[output.write(chap.format(Trims2ts[i], i + 1,
|
||||
chapter_names[i])) for i in range(len(Trims2ts))]
|
||||
if o.verbose:
|
||||
print("Writing {} Chapters to {}". format(chapter_type,
|
||||
o.chapters))
|
||||
|
||||
|
||||
def fmt_time(ts, msp=False):
|
||||
"""Converts nanosecond timestamps to timecodes.
|
||||
|
||||
msp = Set timecodes for millisecond precision if True
|
||||
|
||||
"""
|
||||
s = ts / 10 ** 9
|
||||
m = s // 60
|
||||
s = s % 60
|
||||
h = m // 60
|
||||
m = m % 60
|
||||
if msp:
|
||||
return '{:02.0f}:{:02.0f}:{:06.3f}'.format(h, m, s)
|
||||
else:
|
||||
return '{:02.0f}:{:02.0f}:{:012.9f}'.format(h, m, s)
|
||||
|
||||
|
||||
def truncate(ts, scale=0):
|
||||
"""Truncates a ns timestamp to 0.1*scale precision
|
||||
with an extra decimal place if it rounds up.
|
||||
|
||||
Default: 0 (0.1 ms)
|
||||
|
||||
Examples: 3 (0.1 µs); 6 (0.1 ns)
|
||||
|
||||
"""
|
||||
scale = abs(6 - scale)
|
||||
ots = ts / 10 ** scale
|
||||
tts = (floor(ots * 10) * 10 if round(ots, 1) == floor(ots * 10) / 10 else
|
||||
ceil(ots * 10) * 10 - 5)
|
||||
return int(tts * 10 ** (scale - 2))
|
||||
|
||||
|
||||
def correct_to_ntsc(fps, ms=False):
|
||||
"""Rounds framerate to NTSC values if close enough.
|
||||
Takes and returns a Rational number.
|
||||
|
||||
Ported from FFMS2.
|
||||
"""
|
||||
fps = Fraction(fps).limit_denominator(10**6)
|
||||
fps_list = (24, 25, 30, 48, 50, 60, 100, 120)
|
||||
|
||||
for fps_idx in fps_list:
|
||||
delta = (fps_idx - fps_idx / 1.001) / 2.0
|
||||
if abs(fps - fps_idx) < delta:
|
||||
fps = Fraction(fps_idx, 1)
|
||||
break
|
||||
elif (fps_idx % 25) and (abs(fps - fps_idx / 1.001) < delta):
|
||||
fps = Fraction(fps_idx * 1000, 1001)
|
||||
break
|
||||
|
||||
if not ms:
|
||||
return fps
|
||||
else:
|
||||
return float(1000 / fps)
|
||||
|
||||
|
||||
def convert_v1_to_v2(v1, max, asm, v2=None, first=0):
|
||||
"""Converts a given v1 timecodes file to v2 timecodes.
|
||||
|
||||
Original idea from tritical's tcConv.
|
||||
|
||||
"""
|
||||
ts = fn1 = fn2 = last = 0
|
||||
asm = correct_to_ntsc(asm, True)
|
||||
o = []
|
||||
ap = o.append
|
||||
en = str.encode
|
||||
for line in v1:
|
||||
ovr = line.split(',')
|
||||
if len(ovr) == 3:
|
||||
fn1, fn2, fps = ovr
|
||||
fn1 = int(fn1)
|
||||
fn2 = int(fn2)
|
||||
ovf = correct_to_ntsc(fps, True)
|
||||
while (last < fn1 and last < max):
|
||||
ap(ts)
|
||||
last, ts = last + 1, ts + asm
|
||||
while (last <= fn2 and last < max):
|
||||
ap(ts)
|
||||
last, ts = last + 1, ts + ovf
|
||||
while last < max:
|
||||
ap(ts)
|
||||
last, ts = last + 1, ts + asm
|
||||
if v2:
|
||||
with open(v2, 'wb') as v2f:
|
||||
from os import linesep as ls
|
||||
header = [en('# timecode format v2' + ls)] if first == 0 else [b'']
|
||||
v2f.writelines(header + [en(('{0:3.6f}'.format(s)) + ls) for s in
|
||||
o[first:]])
|
||||
return o[first:]
|
||||
|
||||
|
||||
def parse_tc(tcfile, max=0, otc=None, first=0):
|
||||
"""Parses a timecodes file or cfr fps.
|
||||
|
||||
tcfile = timecodes file or cfr fps to parse
|
||||
max = number of frames to be created in v1 parsing
|
||||
otc = output v2 timecodes filename
|
||||
|
||||
"""
|
||||
|
||||
cfr_re = compile('(\d+(?:\.\d+)?)(?:/|:)?(\d+(?:\.\d+)?)?')
|
||||
vfr_re = compile('# time(?:code|stamp) format (v1|v2)')
|
||||
|
||||
ret = cfr_re.search(tcfile)
|
||||
if ret and not isfile(tcfile):
|
||||
type = 'cfr'
|
||||
num = Fraction(ret.group(1))
|
||||
den = Fraction(ret.group(2)) if ret.group(2) else 1
|
||||
timecodes = Fraction(num, den)
|
||||
if otc:
|
||||
convert_v1_to_v2([], max + 2, timecodes, otc, first)
|
||||
|
||||
else:
|
||||
type = 'vfr'
|
||||
with open(tcfile) as tc:
|
||||
v1 = tc.readlines()
|
||||
ret = vfr_re.search(v1.pop(0))
|
||||
version = ret.group(1) if ret else exit('File is not in a supported '
|
||||
'format.')
|
||||
|
||||
if version == 'v1':
|
||||
ret = v1.pop(0).split(' ')
|
||||
asm = ret[1] if len(ret) == 2 else exit('there is no assumed fps')
|
||||
if v1:
|
||||
ret = convert_v1_to_v2(v1, max, asm, otc, first)
|
||||
timecodes = ['{0:3.6f}\n'.format(i) for i in ret]
|
||||
else:
|
||||
timecodes = correct_to_ntsc(asm)
|
||||
type = 'cfr'
|
||||
if otc:
|
||||
convert_v1_to_v2([], max + 2, timecodes, otc, first)
|
||||
|
||||
elif version == 'v2':
|
||||
if max > len(v1):
|
||||
temp_max = len(v1)
|
||||
sample = temp_max // 100
|
||||
average = 0
|
||||
for i in range(-sample, 0):
|
||||
average += round(float(v1[i]) - float(v1[i - 1]), 6)
|
||||
fps = correct_to_ntsc(Fraction.from_float(sample / average *
|
||||
1000))
|
||||
ret = convert_v1_to_v2([], max - len(v1) + 1, fps, first=1)
|
||||
if v1[-1][-1] is not '\n':
|
||||
v1[-1] += '\n'
|
||||
v1 += ['{0:3.6f}\n'.format(i + float(v1[-1])) for i in ret]
|
||||
timecodes = v1
|
||||
|
||||
return (timecodes, type), max
|
||||
|
||||
|
||||
def get_ts(fn, tc, scale=0):
|
||||
"""Returns timestamps from a frame number and timecodes file or cfr fps
|
||||
|
||||
fn = frame number
|
||||
tc = (timecodes list or Fraction(fps),tc_type)
|
||||
|
||||
scale default: 0 (ns)
|
||||
examples: 3 (µs); 6 (ms); 9 (s)
|
||||
|
||||
"""
|
||||
scale = 9 - scale
|
||||
tc, tc_type = tc
|
||||
if tc_type == 'cfr':
|
||||
ts = round(10 ** scale * fn * Fraction(tc.denominator, tc.numerator))
|
||||
return ts
|
||||
elif tc_type == 'vfr':
|
||||
ts = round(float(tc[fn]) * 10 ** (scale - 3))
|
||||
return ts
|
||||
|
||||
|
||||
def convert_fps(ofn, old, new, oldts=None):
|
||||
"""Returns a frame number from fps and ofps (ConvertFPS)
|
||||
|
||||
fn = frame number
|
||||
old = original fps ('30000/1001', '25')
|
||||
new = output fps ('24000/1001', etc.)
|
||||
|
||||
"""
|
||||
|
||||
newts = 0
|
||||
nfn = 0
|
||||
thr = get_ts(1, old) # milliseconds
|
||||
newframes = []
|
||||
newtimestamps = []
|
||||
temp = temp2 = []
|
||||
|
||||
for i in ofn:
|
||||
for j in i:
|
||||
temp.append(j)
|
||||
|
||||
ofn = temp
|
||||
|
||||
if not oldts:
|
||||
oldtsi = []
|
||||
for fn in ofn:
|
||||
oldtsi.append(get_ts(fn, old))
|
||||
else:
|
||||
oldtsi = []
|
||||
for i in oldts:
|
||||
for j in i:
|
||||
oldtsi.append(j)
|
||||
|
||||
for i in range(len(ofn)):
|
||||
|
||||
fn = ofn[i]
|
||||
ots = oldtsi[i]
|
||||
nts = get_ts(nfn, new)
|
||||
if ots - nts >= thr:
|
||||
while (ots - nts > thr):
|
||||
nfn += 1
|
||||
nts = get_ts(nfn, new)
|
||||
if len(newframes) != 0 and nfn == newframes[-1]:
|
||||
newframes[-1] -= 1
|
||||
newtimestamps[-1] = get_ts(newframes[-1], new)
|
||||
newframes.append(nfn)
|
||||
newtimestamps.append(nts)
|
||||
else:
|
||||
newframes.append(nfn)
|
||||
newtimestamps.append(nts)
|
||||
elif ots - nts < thr:
|
||||
if len(newframes) != 0 and nfn == newframes[-1]:
|
||||
newframes[-1] -= 1
|
||||
newframes.append(nfn)
|
||||
newtimestamps.append(nts)
|
||||
else:
|
||||
newframes.append(nfn)
|
||||
newtimestamps.append(nts)
|
||||
else:
|
||||
nfn = 0
|
||||
while (ots - nts > thr):
|
||||
nfn += 1
|
||||
nts = get_ts(nfn, new)
|
||||
newframes.append(nfn)
|
||||
newtimestamps.append(nts)
|
||||
|
||||
if len(newframes) % 2 == 0:
|
||||
temp = []
|
||||
temp2 = []
|
||||
for i in range(0, len(newframes), 2):
|
||||
temp.append([newframes[i], newframes[i + 1]])
|
||||
temp2.append([newtimestamps[i], newtimestamps[i + 1]])
|
||||
newframes, newtimestamps = temp, temp2
|
||||
|
||||
if oldts:
|
||||
return newframes, newtimestamps
|
||||
else:
|
||||
return newframes
|
||||
|
||||
|
||||
def parse_avs(avs, label=None, reverse=None, line_number=None, clip=None):
|
||||
"""Parse an avisynth file. Scours it for the first uncommented trim line.
|
||||
|
||||
By default it looks for case-insensitive 'trim'. Using label, you can make
|
||||
it parse only the line starting with a certain case of trim, ignoring the
|
||||
others. Ex: label = 'tRiM' looks for the line starting with tRiM, ignoring
|
||||
other cases.
|
||||
|
||||
Returns a list with pairs of frames containing the first and last of each
|
||||
trim.
|
||||
|
||||
"""
|
||||
|
||||
Trims = []
|
||||
|
||||
trim_label = 'trim'
|
||||
comment = ''
|
||||
ignore_case = '(?i)'
|
||||
trim_clip = ''
|
||||
|
||||
if line_number:
|
||||
label = None
|
||||
if label and label.lower() == 'trim':
|
||||
trim_label = label
|
||||
comment = ''
|
||||
ignore_case = ''
|
||||
elif label:
|
||||
comment = '#.*' + label
|
||||
if clip:
|
||||
trimre = compile('(?<!#)(?:{0}\.trim\(|trim\({0}\s*,\s*)(\d+)\s*,\s*(-?\d+)\)(?i)'.format(clip))
|
||||
findTrims = compile("(?<!#)[^#]*(?:{1}\.{0}\(|{0}\({1}\s*,\s*)(\d+)\s*,"
|
||||
"\s*(-?\d+)\).*{2}{3}".format(trim_label, clip, comment, ignore_case))
|
||||
else:
|
||||
trimre = compile('(?<!#)(?:\w+\.)?trim\((?:\w+\s*,\s*)?(\d+)\s*,\s*(-?\d+)\)(?i)'.format(clip))
|
||||
findTrims = compile("(?<!#)[^#]*\s*\.?\s*{0}\((?:\w+\s*,\s*)?(\d+)\s*,"
|
||||
"\s*(-?\d+)\).*{1}{2}".format(trim_label, comment, ignore_case))
|
||||
|
||||
with open(avs) as avsfile:
|
||||
avs = avsfile.readlines()
|
||||
if line_number:
|
||||
avs = avs[line_number - 1:line_number]
|
||||
for line in avs if not reverse else reversed(avs):
|
||||
if findTrims.match(line):
|
||||
Trims = trimre.findall(line)
|
||||
break
|
||||
if not Trims:
|
||||
if label:
|
||||
exit("Error: Avisynth script has no uncommented trims with label "
|
||||
"'{}'".format(label))
|
||||
if line_number:
|
||||
exit("Error: Avisynth script has no uncommented trims on line {}"
|
||||
.format(line_number))
|
||||
if clip:
|
||||
exit("Error: Avisynth script has no uncommented trims with clip "
|
||||
"'{}'".format(clip))
|
||||
exit("Error: Avisynth script has no uncommented trims")
|
||||
|
||||
return Trims
|
||||
|
||||
|
||||
def parse_trims(avs, fps, outfps=None, otc=None, input=None, label=None,
|
||||
reverse=None, line_number=None, clip=None, merge=True):
|
||||
"""Parse trims from an avisynth file.
|
||||
|
||||
Returns 5 lists containing:
|
||||
Trims = Trims as parsed from the avs without processing.
|
||||
Trimsts = Timecodes of each trim in Trims.
|
||||
Trims2 = Offset trims. If ofps is set, they will be using ofps's frame
|
||||
numbers.
|
||||
Trims2ts = Same as Trimsts only for Trims2.
|
||||
audio = Timecodes where each cut will be performed for audio.
|
||||
If the cuts are for adjacent frames they won't be appended to the
|
||||
list, so as to avoid unnecessary cuts.
|
||||
Ex: trim(0,10)+trim(11,20) will be output as trim(0,20)
|
||||
|
||||
"""
|
||||
|
||||
Trims = parse_avs(avs, label, reverse, line_number, clip)
|
||||
audio = []
|
||||
Trimsts = []
|
||||
Trims2 = []
|
||||
Trims2ts = []
|
||||
nt1 = len(Trims)
|
||||
adjacent = False
|
||||
|
||||
# Parse timecodes/fps
|
||||
last_frame = int(Trims[-1][1])
|
||||
if last_frame < 0:
|
||||
last_frame = int(Trims[-1][0]) - int(Trims[-1][1]) - 1
|
||||
elif last_frame == 0:
|
||||
last_frame = int(Trims[-1][0])
|
||||
|
||||
tc, max = parse_tc(fps, last_frame + 2, otc)
|
||||
if tc[1] == 'vfr' and outfps:
|
||||
exit("Can't use --ofps with timecodes file input")
|
||||
if outfps and fps != outfps:
|
||||
ofps = parse_tc(outfps, int(Trims[-1][1]) + 2)[0]
|
||||
if otc:
|
||||
max = convert_fps([[int(Trims[-1][1])]], tc, ofps)[0]
|
||||
parse_tc(outfps, max + 2, otc + '.ofps.txt')
|
||||
|
||||
# Parse trims
|
||||
for i in range(nt1):
|
||||
fn1 = int(Trims[i][0])
|
||||
fn1ts = get_ts(fn1, tc)
|
||||
fn1tsaud = get_ts(fn1, tc)
|
||||
fn2 = int(Trims[i][1])
|
||||
if fn2 > 0:
|
||||
fn2ts = get_ts(fn2 + 1, tc)
|
||||
fn2tsaud = get_ts(fn2 + 1, tc)
|
||||
adjacent = False
|
||||
Trimsts.append((fmt_time(fn1ts), fmt_time(fn2ts)))
|
||||
elif fn2 < 0:
|
||||
fn2 = fn1 - fn2 - 1
|
||||
fn2ts = get_ts(fn2 + 1, tc)
|
||||
fn2tsaud = get_ts(fn2 + 1, tc)
|
||||
adjacent = False
|
||||
Trimsts.append((fmt_time(fn1ts), fmt_time(fn2ts)))
|
||||
else:
|
||||
fn2ts = 0
|
||||
fn2tsaud = 0
|
||||
Trimsts.append((fmt_time(fn1ts), 0))
|
||||
|
||||
# Calculate offsets for non-continuous trims
|
||||
if i == 0:
|
||||
offset = 0
|
||||
offsetts = 0
|
||||
# If the first trim doesn't start at 0
|
||||
if fn1 > 0:
|
||||
offset = fn1
|
||||
offsetts = fn1ts
|
||||
else:
|
||||
# If it's not the first trim
|
||||
last = int(Trims[i - 1][1])
|
||||
lastts = get_ts(last + 1, tc)
|
||||
adjacent = True if not fn1 - (last + 1) else False
|
||||
offset += fn1 - (last + 1)
|
||||
offsetts += 0 if adjacent else fn1ts - lastts
|
||||
|
||||
if input:
|
||||
# Make list with timecodes to cut audio
|
||||
if adjacent and merge:
|
||||
del audio[-1]
|
||||
elif fn1 <= max:
|
||||
audio.append(fmt_time(fn1tsaud))
|
||||
|
||||
if fn2 <= max and fn2 != 0:
|
||||
audio.append(fmt_time(fn2tsaud))
|
||||
|
||||
# Apply the offset to the trims
|
||||
fn1 -= offset
|
||||
fn2 -= offset if fn2 else 0
|
||||
fn1ts -= offsetts
|
||||
fn2ts -= offsetts if fn2 else 0
|
||||
|
||||
# Add trims and their timestamps to list
|
||||
Trims2.append([fn1, fn2])
|
||||
Trims2ts.append((fn1ts, fn2ts))
|
||||
|
||||
# Convert fps if ofps is supplied
|
||||
if outfps and fps != outfps:
|
||||
Trims2, Trims2ts = convert_fps(Trims2, tc, ofps, Trims2ts)
|
||||
|
||||
return Trims, Trimsts, Trims2, Trims2ts, audio
|
||||
|
||||
|
||||
def write_qpfile(qpfile, trims, idr=False):
|
||||
"""Writes keyframes for use in x264 from a list of Trims."""
|
||||
|
||||
with open(qpfile, "w") as qpf:
|
||||
if trims[0][0] == 0:
|
||||
del trims[0]
|
||||
for trim in trims:
|
||||
qpf.write('{0} {1}\n'.format(trim[0], 'I' if idr else 'K'))
|
||||
|
||||
def split_audio(trims, input_file, output_file=None, delay=None, sbr=False,
|
||||
merge=True, remove=True, verbose=False, test=False):
|
||||
from subprocess import call, check_output
|
||||
from sys import getfilesystemencoding
|
||||
import json
|
||||
|
||||
sep = ',+' if merge else ','
|
||||
final_part = ''
|
||||
if len(trims) % 2 != 0:
|
||||
final_part = '{}{}-'.format(sep if len(trims) > 1 else "", trims.pop())
|
||||
cuttimes = sep.join(['{}-{}'.format(trims[i], trims[i + 1]) for i in range(0,len(trims),2)])
|
||||
cuttimes += final_part
|
||||
|
||||
ident = check_output([mkvmerge, "--identify", "-F", "json", input_file])
|
||||
tid = 0
|
||||
try:
|
||||
info = json.loads(ident)
|
||||
for track in info["tracks"]:
|
||||
if track.get("type") == "audio":
|
||||
tid = track.get("id", 0)
|
||||
sbr = track.get("properties", {}).get("aac_is_sbr", False)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# determine delay
|
||||
delre = compile('DELAY ([-]?\d+)')
|
||||
ret = delre.search(input_file)
|
||||
delay = ('{0}:{1}'.format(tid, delay if delay else ret.group(1))
|
||||
if delay or ret else None)
|
||||
|
||||
cutCmd = [mkvmerge, '-o', output_file]
|
||||
cutCmd.extend([input_file, '--split'])
|
||||
cutCmd.extend(['parts:' + cuttimes])
|
||||
if delay:
|
||||
cutCmd.extend(['--sync', delay])
|
||||
if sbr:
|
||||
cutCmd.extend(['--aac-is-sbr', str(tid)])
|
||||
|
||||
if verbose:
|
||||
print('Cutting: {0}\n'.format(
|
||||
' '.join(['"{0}"'.format(i) for i in cutCmd])))
|
||||
else:
|
||||
cutCmd.append('-q')
|
||||
|
||||
if not test:
|
||||
cutExec = call(cutCmd)
|
||||
if cutExec == 1:
|
||||
print("Mkvmerge exited with warnings: {0:d}".format(cutExec))
|
||||
elif cutExec == 2:
|
||||
exit("Failed to execute mkvmerge: {0:d}".format(cutExec))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(argv[1:])
|
BIN
bin/vfr_old.exe
BIN
bin/vfr_old.exe
Binary file not shown.
Binary file not shown.
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup useLegacyV2RuntimeActivationPolicy="true">
|
||||
<supportedRuntime version="v4.0" />
|
||||
<supportedRuntime version="v2.0" />
|
||||
</startup>
|
||||
<appSettings>
|
||||
<add key="EnableWindowsFormsHighDpiAutoResizing" value="true"/>
|
||||
</appSettings>
|
||||
<runtime>
|
||||
<AppContextSwitchOverrides value="Switch.System.IO.BlockLongPaths=false;Switch.System.IO.UseLegacyPathHandling=false"/>
|
||||
</runtime>
|
||||
</configuration>
|
Binary file not shown.
Before Width: | Height: | Size: 17 KiB |
|
@ -1,2 +0,0 @@
|
|||
mkvtoolnix.path=H:\\- 1 Encode\\Encod.es\\mkvtoolnix_v1900\\
|
||||
vfr.path=G:\\BACKUP\\Desktop\\Tes\\Portable Programming\\PowerShell\\AudioSplitter2\\res\\
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 318 KiB |
BIN
res/AS.ico
BIN
res/AS.ico
Binary file not shown.
Before Width: | Height: | Size: 17 KiB |
|
@ -1,2 +0,0 @@
|
|||
mkvtoolnix.path=H:\\- 1 Encode\\Encod.es\\mkvtoolnix_v1900\\
|
||||
vfr.path=G:\\BACKUP\\Desktop\\Tes\\Portable Programming\\PowerShell\\AudioSplitter2\\res\\
|
BIN
res/python32.dll
BIN
res/python32.dll
Binary file not shown.
BIN
res/vfr.exe
BIN
res/vfr.exe
Binary file not shown.
BIN
res/yuki.gif
BIN
res/yuki.gif
Binary file not shown.
Before Width: | Height: | Size: 318 KiB |
Loading…
Add table
Reference in a new issue