(rebuilding history) v3.0
Rewritten in PHP Feature: Many more features now available via command-line arguments
This commit is contained in:
parent
3c663d87dd
commit
65ddc2dfde
@ -1,39 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
while (( "$#" )); do
|
||||
|
||||
if [[ $1 == "--graphical" ]] ; then
|
||||
shift
|
||||
xfce4-terminal -x bash $(basename "$0") "$@"
|
||||
exit 0
|
||||
|
||||
elif [[ -d $1 ]] ; then
|
||||
cd $1
|
||||
|
||||
else
|
||||
echo "Unknown argument" >&2
|
||||
exit 1
|
||||
|
||||
fi
|
||||
|
||||
shift
|
||||
|
||||
done
|
||||
|
||||
rren -r '*.mkv' _ " "
|
||||
rren -e '[*.mkv' "\[(.+?)\]\s+([^\[]+)\s+(?:\[(.*)\])?\.([a-z0-9]{3})" "\2 [\1 \3].\4"
|
||||
rren -r '*.mkv' "] [" " "
|
||||
rren -r '*.mkv' "][" " "
|
||||
|
||||
pause
|
||||
|
||||
##
|
||||
## Original _shunt_tags.cmd content:
|
||||
##
|
||||
## @rren -r *.mkv _ " "
|
||||
## @rren -e [*.mkv "\[([^]+?)\]\s+([^\[]+)\s+(?:\[(.*)\])?\.([a-z0-9]{3})" "$2 [$1 $3].$4"
|
||||
## @rren -r *.mkv "] [" " "
|
||||
## @rren -r *.mkv "][" " "
|
||||
## @pause
|
||||
##
|
||||
|
637
shunt_tags
Executable file
637
shunt_tags
Executable file
@ -0,0 +1,637 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (2017) The shunt_tags author(s)
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
// TODO kioclient integration
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 'On');
|
||||
|
||||
function fixup($input) {
|
||||
|
||||
$tmp = $input;
|
||||
|
||||
$extension = substr($tmp, strlen($tmp) - 4);
|
||||
$tmp = substr($tmp, 0, strlen($tmp) - 4);
|
||||
|
||||
// Some files using period instead of space
|
||||
if (strpos($tmp, ' ') === false) {
|
||||
// Except when the period is real:
|
||||
// - in a number string like EP##.#
|
||||
// - ellipsis (...)
|
||||
// Find all the \d.\d and store indexes
|
||||
$matches = [];
|
||||
preg_match_all('~\d\.\d~', $tmp, $matches, PREG_OFFSET_CAPTURE);
|
||||
$match_ellipsis = [];
|
||||
preg_match_all('~\.\.\.+~', $tmp, $match_ellipsis, PREG_OFFSET_CAPTURE);
|
||||
|
||||
// Replace
|
||||
$tmp = str_replace('.', ' ', $tmp);
|
||||
|
||||
// Restore indexes
|
||||
foreach($matches[0] as $match) {
|
||||
$tmp[$match[1] + 1] = '.';
|
||||
}
|
||||
|
||||
foreach($match_ellipsis[0] as $match) {
|
||||
$tmp[$match[1] + 1] = '.';
|
||||
$tmp[$match[1] + 2] = '.';
|
||||
$tmp[$match[1] + 3] = '.'; // although we matched even longer sequences...
|
||||
}
|
||||
}
|
||||
|
||||
$fixed_point_iterate = function($tmp, $cb) {
|
||||
$seen = [];
|
||||
do {
|
||||
$seen[$tmp] = true;
|
||||
$tmp = $cb($tmp);
|
||||
|
||||
} while (! array_key_exists($tmp, $seen));
|
||||
return $tmp;
|
||||
};
|
||||
|
||||
// Some files using underscore instead of space
|
||||
// Except - preserve "G_P", that's a known sub group
|
||||
// Iterate until fixed point
|
||||
$tmp = $fixed_point_iterate($tmp, function($tmp) {
|
||||
return preg_replace_callback('~(._.)~', function($matches) {
|
||||
if ($matches[1] == "G_P") {
|
||||
return $matches[1]; // allow this case
|
||||
} else {
|
||||
return $matches[1][0].' '.$matches[1][2];
|
||||
}
|
||||
}, $tmp);
|
||||
});
|
||||
|
||||
// Wrap loose tags
|
||||
$tmp = preg_replace('~\b((?:gg\-|UTW\-)?THORA)\b~', ' [\1] ', $tmp);
|
||||
$tmp = preg_replace('~\bXVID\b~', ' [XVID] ', $tmp);
|
||||
$tmp = preg_replace('~\b1080p\b~', ' [1080p] ', $tmp);
|
||||
$tmp = preg_replace('~([^p])v2\b~i', '\1 [v2] ', $tmp); // They could be part of the episode number - pull them out - BUT don't pull out "PV2"
|
||||
$tmp = preg_replace('~([^p])v3\b~i', '\1 [v3] ', $tmp);
|
||||
$tmp = preg_replace('~([^p])v4\b~i', '\1 [v4] ', $tmp);
|
||||
|
||||
// Remove nesting
|
||||
$drop_nested_tags = function($tmp) {
|
||||
$depth = 0;
|
||||
$ret = '';
|
||||
for ($i = 0; $i < strlen($tmp); ++$i) {
|
||||
if ($tmp[$i] == '[') {
|
||||
$depth += 1;
|
||||
if ($depth === 1) {
|
||||
$ret .= '[';
|
||||
}
|
||||
} else if ($tmp[$i] == ']') {
|
||||
$depth -= 1;
|
||||
if ($depth === 0) {
|
||||
$ret .= ']';
|
||||
}
|
||||
} else {
|
||||
$ret .= $tmp[$i];
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
};
|
||||
$tmp = $drop_nested_tags($tmp);
|
||||
|
||||
// Convert round brackets to square brackets only if they contain tags
|
||||
$tmp = str_replace(' ASS(EN)', '', $tmp); // messes with the (=>[, not an important tag anyway
|
||||
$tmp = preg_replace('~\(([^\)]*(?:G_P|B-A|R2J|NakamaSub|720|1080|264|BD|DVD|AAC|AC3|Dual Audio|XVID|divx|[A-F0-9]{8})[^\)]*)\)~i', '[\1]', $tmp);
|
||||
|
||||
// Work around nightmare sub group name
|
||||
$tmp = str_replace('[Saiyan]BrollY', 'SaiyanBrollY', $tmp);
|
||||
|
||||
// Move tags from front to end
|
||||
if ($tmp[0] == '[') {
|
||||
$tmp = preg_replace('~^(\[.+?\])\s?([^\[]+)(.*)$~', '\2 \1 \3', $tmp);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
$cleanup = [
|
||||
'][' => ' ',
|
||||
'] [' => ' ',
|
||||
'] - [' => ' ',
|
||||
' ' => ' ',
|
||||
' - [' => '[',
|
||||
'()' => '',
|
||||
'( )' => '',
|
||||
'720x480' => '480p',
|
||||
'1280x720' => '720p',
|
||||
'1920x1080' => '1080p',
|
||||
'BluRay' => 'BD',
|
||||
'Blu-Ray' => 'BD',
|
||||
'BDrip' => 'BD',
|
||||
'bd-rip' => 'BD',
|
||||
'Clean Ending' => 'NCED',
|
||||
'Clean Opening' => 'NCOP',
|
||||
'Creditless Opening' => 'NCOP',
|
||||
'Creditless Ending' => 'NCED',
|
||||
'Creditless OP' => 'NCOP',
|
||||
'Creditless ED' => 'NCED',
|
||||
];
|
||||
$tmp = $fixed_point_iterate($tmp, function($tmp) use ($cleanup) {
|
||||
|
||||
// Clean up tags
|
||||
$tmp = str_replace(array_keys($cleanup), array_values($cleanup), $tmp);
|
||||
|
||||
// Move tags from middle to end
|
||||
$tmp = preg_replace('~([^\[\]]+)(\[[^\]]+?\])([^\[\]]+)~', '\1\3\2', $tmp);
|
||||
|
||||
return $tmp;
|
||||
});
|
||||
/*
|
||||
// Move tags from middle to end
|
||||
$tmp = $fixed_point_iterate($tmp, function($tmp) {
|
||||
return preg_replace('~([^\[\]]+)(\[[^\]]+?\])([^\[\]]+)~', '\1\3\2', $tmp);
|
||||
});
|
||||
*/
|
||||
// Cleanup commas inside tags ( [720p,Bluray] => [720p Bluray] )
|
||||
$tmp = preg_replace_callback('~\[(.+)\]~', function($matches) { return str_replace(',', ' ', $matches[0]); }, $tmp);
|
||||
|
||||
// Remove trailing spaces before file extension
|
||||
$tmp = trim($tmp); //$tmp = preg_replace('~\s+(\..{3})$~', '\1', $tmp);
|
||||
|
||||
// Episode numbers
|
||||
// #xx, Exx, EPxx, Ep.XX, Ep. XX, EXXv2... but not a year like 1970 or 2013
|
||||
|
||||
$add_episode_hyphen_if_not_year = function($matches) {
|
||||
$is_year = (intval($matches[1]) > 1000);
|
||||
if ($is_year) {
|
||||
return $matches[0]; // as-is
|
||||
} else {
|
||||
return ' - '.$matches[1];
|
||||
}
|
||||
};
|
||||
|
||||
$tmp = preg_replace_callback('~ (?:#|E(?:P(?:\. ?)?)?)(\d[\dv]*)\b~i', $add_episode_hyphen_if_not_year, $tmp);
|
||||
|
||||
$got_episode_number = function($tmp) {
|
||||
if (preg_match('~ \- S?[\d]+~', $tmp)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allow "Lesson XX" (GTO format), "OVA XX", "ONA XX"
|
||||
if (preg_match('~ \- (?:Lesson|OVA|ONA) [\d]+~', $tmp)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if (! $got_episode_number($tmp)) {
|
||||
$tmp = preg_replace_callback('~ episode (\d+)~', function($matches) { return sprintf(' - %02d', $matches[1]); }, $tmp);
|
||||
}
|
||||
|
||||
if (! $got_episode_number($tmp)) {
|
||||
// Last ditch - replace any lone 1+ digit number (but also support v2 and 0.5 episodes)
|
||||
$tmp = preg_replace_callback('~ (\d\d*(?:[v\.]\d\d?)?)\b~', $add_episode_hyphen_if_not_year, $tmp);
|
||||
}
|
||||
|
||||
// Two-digit episode numbers even for 0.5 and 6.75 episodes
|
||||
$tmp = preg_replace('~ - (\d)\b~', ' - 0\1', $tmp);
|
||||
$tmp = preg_replace('~ - (\d\.\d\d?)\b~', ' - 0\1', $tmp);
|
||||
|
||||
// Anything after the episode number but before the tag is an episode title
|
||||
// Enforce dash between them if it's not there already
|
||||
$tmp = preg_replace('~ - (\d[\dv\.]+) ([^\[\-\(])~', ' - \1 - \2', $tmp);
|
||||
|
||||
// Fix double dashes
|
||||
//$tmp = str_replace('- -', '-', $tmp);
|
||||
|
||||
// Remove unnecessary quotes
|
||||
$tmp = preg_replace("~ - '(.+?)' ([-\\[])~", ' - \1 \2', $tmp);
|
||||
|
||||
// From the first [ to the last ], no other [] should appear within, and there should be no extraneous spaces
|
||||
$tmp = preg_replace_callback('~^([^\[]*?)\[(.+)\]~', function($matches) {
|
||||
return $matches[1].'['.trim(str_replace(['[', ']', ' ', ' - ', '- ', '.'], [' ', ' ', ' ', ' ', ' ', ' '], $matches[2])).']';
|
||||
}, $tmp);
|
||||
|
||||
// There's always a space before the tag
|
||||
$tmp = preg_replace('~([^ ])\[~', '\1 [', $tmp);
|
||||
|
||||
// Fixup [Saiyan]BrollY, that's a sub group
|
||||
$tmp = str_replace('SaiyanBrollY', '[Saiyan]BrollY', $tmp);
|
||||
|
||||
// Fixup terrible case with [m.3.3.w] sub group being detected as an episode number
|
||||
// It would be preferable for this to not happen in the first place
|
||||
$tmp = str_replace('m 03 3 w', 'm.3.3.w', $tmp);
|
||||
|
||||
// Fixup anime_fin, that's a sub group
|
||||
$tmp = str_replace('[anime fin', '[anime_fin', $tmp);
|
||||
|
||||
// Fixup "No. 6", that's a show title
|
||||
$tmp = str_replace('No. - 06 - ', 'No. 6 - ', $tmp);
|
||||
|
||||
// Fixup "Oedo 808", that's a show title
|
||||
$tmp = str_replace('Oedo - 808', 'Oedo 808', $tmp);
|
||||
|
||||
// Fixup "h264-720p"
|
||||
$tmp = str_replace("h264-", "h264 ", $tmp);
|
||||
|
||||
// Reinstate file extension
|
||||
$tmp = $tmp.$extension;
|
||||
|
||||
// Done
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
function tests() {
|
||||
$tests = [
|
||||
'[gg]_Valvrave_the_Liberator_-_12_[F9F3F5C5].mkv'
|
||||
=> 'Valvrave the Liberator - 12 [gg F9F3F5C5].mkv'
|
||||
,
|
||||
'Poyopoyo Kansatsu Nikki - 16 [HorribleSubs][FShahid][46B9A7B5].mkv'
|
||||
=> 'Poyopoyo Kansatsu Nikki - 16 [HorribleSubs FShahid 46B9A7B5].mkv'
|
||||
,
|
||||
'[AHQ] Gundam Seed - 42 - Lacus Strikes.mkv'
|
||||
=> 'Gundam Seed - 42 - Lacus Strikes [AHQ].mkv'
|
||||
,
|
||||
'[Frenchies-Mux]_True_Mazinger_Impact!_Chapter_Z_03_(720p)[7EBA0032].mkv'
|
||||
=> 'True Mazinger Impact! Chapter Z - 03 [Frenchies-Mux 720p 7EBA0032].mkv'
|
||||
,
|
||||
'[RUELL-Next] Natsume Yuujinchou S4 EP09 (BD 1280x720 x264 AAC ASS(EN)) [BE846FE4].mkv'
|
||||
=> 'Natsume Yuujinchou S4 - 09 [RUELL-Next BD 720p x264 AAC BE846FE4].mkv'
|
||||
,
|
||||
'[Nubles] Space Battleship Yamato 2199 (2012) episode 21 [720p 10 bit AAC][C6884514].mkv'
|
||||
=> 'Space Battleship Yamato 2199 (2012) - 21 [Nubles 720p 10 bit AAC C6884514].mkv',
|
||||
|
||||
'[Nubles] Space Battleship Yamato 2199 (2012) episode 8 (720p 10 bit AAC).mkv'
|
||||
=> 'Space Battleship Yamato 2199 (2012) - 08 [Nubles 720p 10 bit AAC].mkv'
|
||||
,
|
||||
"Salaryman_Kintaro_-_15_[A-Et]_(0F83C5FF).mkv"
|
||||
=> "Salaryman Kintaro - 15 [A-Et 0F83C5FF].mkv"
|
||||
,
|
||||
'[Elysium]Shiki.EP15(BD.720p.Hi10P.AAC)[0437F1F6].mkv'
|
||||
=> 'Shiki - 15 [Elysium BD 720p Hi10P AAC 0437F1F6].mkv'
|
||||
,
|
||||
'[Elysium]Shiki.EP20.5(BD.720p.Hi10P.AAC)[BFE4B6BF].mkv'
|
||||
=> 'Shiki - 20.5 [Elysium BD 720p Hi10P AAC BFE4B6BF].mkv'
|
||||
,
|
||||
'(G_P) Phoenix 03(x264)(17A173E7).mkv'
|
||||
=> 'Phoenix - 03 [G_P x264 17A173E7].mkv'
|
||||
,
|
||||
'[Commie] Teekyuu - OVA 1 [BD 720p AAC] [58F19BF2].mkv'
|
||||
=> 'Teekyuu - OVA 1 [Commie BD 720p AAC 58F19BF2].mkv'
|
||||
,
|
||||
'[Kametsu]_Your_Lie_in_April_11v2_[Blu-Ray][720p][Hi10][588D1B1F].mkv'
|
||||
=> 'Your Lie in April - 11 [Kametsu v2 BD 720p Hi10 588D1B1F].mkv'
|
||||
,
|
||||
'Patlabor.TV.38v2.(Dual.Audio).XVID.[AM].ogm'
|
||||
=> 'Patlabor TV - 38 [v2 Dual Audio XVID AM].ogm'
|
||||
,
|
||||
'Laputa_Castle_in_the_Sky_(1986)_[720p,BluRay,x264]_-_THORA.mkv'
|
||||
=> 'Laputa Castle in the Sky (1986) [720p BD x264 THORA].mkv'
|
||||
,
|
||||
'[HorribleSubs] 91 Days - 7.5 [720p].mkv'
|
||||
=> '91 Days - 07.5 [HorribleSubs 720p].mkv'
|
||||
,
|
||||
'Code_Geass_Ep01_The_Day_the_Demon_was_Born_[720p,BluRay,x264]_-_gg-THORA.mkv'
|
||||
=> 'Code Geass - 01 - The Day the Demon was Born [720p BD x264 gg-THORA].mkv'
|
||||
,
|
||||
'[Z-Z] Blood + Ep. 01 - First Kiss.mkv'
|
||||
=> 'Blood + - 01 - First Kiss [Z-Z].mkv'
|
||||
,
|
||||
'Code_Geass_Picture_Drama_6.75_[720p,BluRay,x264]_-_gg-THORA v2.mkv'
|
||||
=> 'Code Geass Picture Drama - 06.75 [720p BD x264 gg-THORA v2].mkv'
|
||||
,
|
||||
'Baccano - 14 (OVA).mkv'
|
||||
=> 'Baccano - 14 (OVA).mkv' // no change expected
|
||||
,
|
||||
'[Jarzka] Cromartie High School 05 - Sentimental Bus [480p 10bit X264 DVD Dual-Audio] [A63E3F52].mkv'
|
||||
=> 'Cromartie High School - 05 - Sentimental Bus [Jarzka 480p 10bit X264 DVD Dual-Audio A63E3F52].mkv'
|
||||
,
|
||||
'Area 88 OVA - 01 [Blitz flawed 5EF8A0E2].mkv'
|
||||
=> 'Area 88 OVA - 01 [Blitz flawed 5EF8A0E2].mkv' // no change expected
|
||||
,
|
||||
'[m.3.3.w]_Genshiken_Nidaime_no_Roku_-_OAD_[v0][A6EF7886].mkv'
|
||||
=> 'Genshiken Nidaime no Roku - OAD [m.3.3.w v0 A6EF7886].mkv'
|
||||
,
|
||||
'Gintama - 21 [Rumbel XviD 3E184082 v2].avi'
|
||||
=> 'Gintama - 21 [Rumbel XviD 3E184082 v2].avi' // no change expected
|
||||
,
|
||||
'Patlabor 1 (1989) [THORA].mkv'
|
||||
=> 'Patlabor - 01 (1989) [THORA].mkv'
|
||||
,
|
||||
'[ACX]Immortal_Grand_Prix_-_01_-_Time_to_Shine_[[Saiyan]BrollY]_[7A73D794].mkv'
|
||||
=> 'Immortal Grand Prix - 01 - Time to Shine [ACX [Saiyan]BrollY 7A73D794].mkv'
|
||||
,
|
||||
'[AnimeNOW] Danshi Koukousei no Nichijou - OP (BD 1280x720 10-bit x264 AAC) [429C5783].mkv'
|
||||
=> 'Danshi Koukousei no Nichijou - OP [AnimeNOW BD 720p 10-bit x264 AAC 429C5783].mkv'
|
||||
,
|
||||
'(B-A)Great_Teacher_Onizuka_-_Lesson_01_(6F68CC7E).mkv'
|
||||
=> 'Great Teacher Onizuka - Lesson 01 [B-A 6F68CC7E].mkv'
|
||||
,
|
||||
"[OZC]Mobile Suit Gundam - The 08th MS Team Blu-ray Box E01 'War for Two' [720p].mkv"
|
||||
=> "Mobile Suit Gundam - The 08th MS Team Blu-ray Box - 01 - War for Two [OZC 720p].mkv"
|
||||
,
|
||||
'Gundam SEED Destiny 14[AEF97E04].avi'
|
||||
=> 'Gundam SEED Destiny - 14 [AEF97E04].avi'
|
||||
,
|
||||
'Gundam SEED Destiny 1.avi'
|
||||
=> 'Gundam SEED Destiny - 01.avi'
|
||||
,
|
||||
'[Exiled-Destiny]_Girls_Bravo_Ep01v3_[R2_Video]_(94D81F22).mkv'
|
||||
=> 'Girls Bravo - 01 [Exiled-Destiny v3 R2 Video 94D81F22].mkv'
|
||||
,
|
||||
'[ACX]Immortal_Grand_Prix_-_11_-_And_Then_..._[[Saiyan]BrollY]_[50EC0CBC].mkv'
|
||||
=> 'Immortal Grand Prix - 11 - And Then ... [ACX [Saiyan]BrollY 50EC0CBC].mkv'
|
||||
,
|
||||
'[DmonHiro] Kyousougiga #00v2 - Recap (BD, 720p) [47986C5A].mkv'
|
||||
=> 'Kyousougiga - 00 - Recap [DmonHiro v2 BD 720p 47986C5A].mkv'
|
||||
,
|
||||
'Legend.of.the.Galactic.Heroes.110.[x264.720p.10bit.AAC].mkv'
|
||||
=> 'Legend of the Galactic Heroes - 110 [x264 720p 10bit AAC].mkv'
|
||||
,
|
||||
'Minipato OVA [anime_fin 2906CE7D].avi'
|
||||
=> 'Minipato OVA [anime_fin 2906CE7D].avi' // no change expected
|
||||
,
|
||||
'[Doki] No. 6 - NCOP (1280x720 h264 BD AAC) [0A4A8A9B].mkv'
|
||||
=> 'No. 6 - NCOP [Doki 720p h264 BD AAC 0A4A8A9B].mkv'
|
||||
,
|
||||
'[RG Genshiken] Gintama - Creditless Ending ep.088-095, 097-099 [DVDRip 704x528 x264 PCM].mkv'
|
||||
=> 'Gintama - NCED - 088-095, 097-099 [RG Genshiken DVDRip 704x528 x264 PCM].mkv'
|
||||
,
|
||||
'Cyber City Oedo 808 - Art Gallery (XviD)[anibalance][4C7F6645].mkv'
|
||||
=> 'Cyber City Oedo 808 - Art Gallery [XviD anibalance 4C7F6645].mkv'
|
||||
,
|
||||
'Death Billiards (Anime Mirai 2013) [gg 29BE9711].mkv'
|
||||
=> 'Death Billiards (Anime Mirai 2013) [gg 29BE9711].mkv' // no change expected
|
||||
,
|
||||
'King of Thorn (Ibara no Ou) [Harth-QTS-v2].mkv'
|
||||
=> 'King of Thorn (Ibara no Ou) [Harth-QTS v2].mkv'
|
||||
,
|
||||
'Lupin III - [fong] Lupin III - Kutabare! Nostradamus [BDrip.720p.10bit.DualAudio].mkv'
|
||||
=> 'Lupin III Lupin III - Kutabare! Nostradamus [fong BD 720p 10bit DualAudio].mkv'
|
||||
,
|
||||
"[JoJo]_Jojo's_Bizarre_Adventure_-_20_[BD][h264-720p_AAC][054C181A].mkv"
|
||||
=> "Jojo's Bizarre Adventure - 20 [JoJo BD h264 720p AAC 054C181A].mkv"
|
||||
,
|
||||
"Ghost in the Shell Arise - 04 [THORA 1080p].mkv"
|
||||
=> "Ghost in the Shell Arise - 04 [THORA 1080p].mkv" // no change expected
|
||||
,
|
||||
"Cowboy_Bebop_Knockin'_on_Heaven's_Door_(2001)_[1080p,BluRay,x264,flac]_-_THORA.mkv"
|
||||
=> "Cowboy Bebop Knockin' on Heaven's Door (2001) [1080p BD x264 flac THORA].mkv"
|
||||
,
|
||||
'[EG]Turn-A_Gundam_01_V2[E8F575A6].mkv'
|
||||
=> 'Turn-A Gundam - 01 [EG v2 E8F575A6].mkv'
|
||||
,
|
||||
'PV2.mp4'
|
||||
=> 'PV2.mp4' // no change expected
|
||||
,
|
||||
'[DmonHiro] Kyousougiga - Clean Ending (v2) (BD, 720p) [F1849601].mkv'
|
||||
=> 'Kyousougiga - NCED [DmonHiro v2 BD 720p F1849601].mkv'
|
||||
,
|
||||
];
|
||||
|
||||
$any_failures = false;
|
||||
|
||||
foreach($tests as $input => $expected) {
|
||||
$result = fixup($input);
|
||||
if ($result !== $expected) {
|
||||
echo "Test failed\n";
|
||||
echo " Input : $input \n";
|
||||
echo " Got : $result \n";
|
||||
echo " Expected : $expected \n";
|
||||
$any_failures = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $any_failures) {
|
||||
echo "All tests passed.\n";
|
||||
}
|
||||
|
||||
return !$any_failures;
|
||||
}
|
||||
|
||||
function findFilesNeedingReplacement($basedir, $recursive, $quiet) {
|
||||
|
||||
$iterator = $recursive
|
||||
? new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir), RecursiveIteratorIterator::SELF_FIRST)
|
||||
: new DirectoryIterator($basedir)
|
||||
;
|
||||
|
||||
if (! $quiet) {
|
||||
echo "Scanning...";
|
||||
}
|
||||
$ct = 0;
|
||||
|
||||
$replacements = [];
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if (! $quiet) {
|
||||
if (++$ct % 100 == 0) {
|
||||
echo ".";
|
||||
}
|
||||
}
|
||||
|
||||
if ($file->isDir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fname = $file->getFilename();
|
||||
|
||||
if (! preg_match("~\.(?:mkv|mp4|avi|ogm|ogv)$~", $fname)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$new = fixup($fname);
|
||||
if ($new !== $fname) {
|
||||
$replacements[] = [
|
||||
$file->getPath(),
|
||||
$fname,
|
||||
$new
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (! $quiet) {
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
// Alphabetic sort
|
||||
usort($replacements, function($a, $b) {
|
||||
$pc = strcasecmp($a[0], $b[0]);
|
||||
if ($pc !== 0) {
|
||||
return $pc;
|
||||
}
|
||||
|
||||
return strcasecmp($a[1], $b[1]);
|
||||
});
|
||||
|
||||
return $replacements;
|
||||
}
|
||||
|
||||
function displayReplacementsTable(array $replacements) {
|
||||
$max_length = 10;
|
||||
foreach($replacements as $r) {
|
||||
$display_width = mb_strwidth($r[1], 'UTF-8'); // strlen($r[1])
|
||||
$max_length = max($max_length, $display_width);
|
||||
}
|
||||
|
||||
foreach($replacements as $r) {
|
||||
echo sprintf("%-{$max_length}s %s\n", $r[1], $r[2]);
|
||||
}
|
||||
}
|
||||
|
||||
function applyReplacements(array $replacements, $echo_undo_script) {
|
||||
$any_errors = false;
|
||||
|
||||
if ($echo_undo_script) {
|
||||
echo "#!/bin/bash\nset -eu\n\n";
|
||||
}
|
||||
|
||||
foreach($replacements as $r) {
|
||||
$src_path = $r[0].'/'.$r[1];
|
||||
$dest_path = $r[0].'/'.$r[2];
|
||||
|
||||
if (file_exists($dest_path)) {
|
||||
if ($echo_undo_script) {
|
||||
echo "# ";
|
||||
}
|
||||
echo "Skipping {$r[1]} (destination path already exists)\n";
|
||||
$any_errors = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
rename($src_path, $dest_path);
|
||||
if ($echo_undo_script) {
|
||||
echo "mv ".escapeshellarg($dest_path)." ".escapeshellarg($src_path)."\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $any_errors;
|
||||
}
|
||||
|
||||
function usage() {
|
||||
echo <<<EOD
|
||||
Usage:
|
||||
shunt_tags [OPTIONS]
|
||||
|
||||
Options:
|
||||
-y --accept Apply updates without prompting. (false)
|
||||
-d --directory DIR Set directory. (pwd)
|
||||
--display table|dump|none Choose display mode. ('table')
|
||||
-n --dry-run Display proposed updates and exit. (false)
|
||||
--help, --usage Display this message
|
||||
--interactive Prompt whether to apply updates. (true)
|
||||
-q --quiet Quiet mode hides scanning progress. (false)
|
||||
-qq --quiet2 Equivalent to "-q --display none"
|
||||
-r --recursive Apply to subdirectories. (false)
|
||||
--test-suite Run test suite and exit
|
||||
--undo Display an undo script
|
||||
|
||||
EOD;
|
||||
die(0);
|
||||
}
|
||||
|
||||
function main($argv) {
|
||||
|
||||
$recursive = false;
|
||||
$directory = __DIR__;
|
||||
$dryrun = false;
|
||||
$shouldprompt = true;
|
||||
$displaymode = "table";
|
||||
$quiet = false;
|
||||
$echo_undo_script = false;
|
||||
|
||||
for($i = 0; $i < count($argv); ++$i) {
|
||||
if ($argv[$i] == "-r" || $argv[$i] == "--recursive") {
|
||||
$recursive = true;
|
||||
|
||||
} else if ($argv[$i] == "-d" || $argv[$i] == "-directory") {
|
||||
$directory = $argv[++$i];
|
||||
|
||||
} else if ($argv[$i] == "--test-suite") {
|
||||
die(tests() ? 0 : 1);
|
||||
|
||||
} else if ($argv[$i] == "--interactive") {
|
||||
$shouldprompt = true;
|
||||
|
||||
} else if ($argv[$i] == "-y" || $argv[$i] == "--accept") {
|
||||
$shouldprompt = false;
|
||||
|
||||
} else if ($argv[$i] == "-n" || $argv[$i] == "--dry-run") {
|
||||
$dryrun = true;
|
||||
|
||||
} else if ($argv[$i] == "--display") {
|
||||
$displaymode = $argv[++$i];
|
||||
|
||||
} else if ($argv[$i] == "-q" || $argv[$i] == "--quiet") {
|
||||
$quiet = true;
|
||||
|
||||
} else if ($argv[$i] == "-qq" || $argv[$i] == "--quiet2") {
|
||||
$quiet = true;
|
||||
$displaymode = "none";
|
||||
|
||||
} else if ($argv[$i] == "--undo") {
|
||||
$echo_undo_script = true;
|
||||
|
||||
} else if ($argv[$i] == "--help" || $argv[$i] == "--usage") {
|
||||
usage();
|
||||
|
||||
} else {
|
||||
echo "Unknown argument {$argv[$i]} (try --help)\n";
|
||||
die(1);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$replacements = findFilesNeedingReplacement($directory, $recursive, $quiet);
|
||||
|
||||
if (count($replacements) === 0) {
|
||||
echo "No candidates for renaming.\n";
|
||||
die(0);
|
||||
}
|
||||
|
||||
switch($displaymode) {
|
||||
case "table": {
|
||||
displayReplacementsTable($replacements);
|
||||
} break;
|
||||
|
||||
case "dump": {
|
||||
var_dump($replacements);
|
||||
} break;
|
||||
|
||||
case "none": break;
|
||||
|
||||
default: {
|
||||
echo "Unknown display mode {$displaymode}\n";
|
||||
die(1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($dryrun) {
|
||||
die(0);
|
||||
}
|
||||
|
||||
if ($shouldprompt) {
|
||||
echo "Apply changes? [y/N] > ";
|
||||
$line = trim(fgets(STDIN));
|
||||
if ($line !== "y") {
|
||||
echo "Not applying changes.\n";
|
||||
die(0);
|
||||
}
|
||||
}
|
||||
|
||||
$any_errors = applyReplacements($replacements, $echo_undo_script);
|
||||
|
||||
die($any_errors ? 1 : 0);
|
||||
}
|
||||
|
||||
main(array_slice($_SERVER['argv'], 1));
|
Loading…
Reference in New Issue
Block a user