feat: Undo bogus formatting changes

This commit is contained in:
Igor Katkov
2025-12-30 23:33:01 -08:00
parent 962929d42d
commit d51f2ce628
5 changed files with 247 additions and 237 deletions

View File

@@ -261,7 +261,7 @@ async def add(request):
if split_by_chapters is None: if split_by_chapters is None:
split_by_chapters = False split_by_chapters = False
if chapter_template is None: if chapter_template is None:
chapter_template = '%(section_number)02d - %(section_title)s.%(ext)s' chapter_template = '%(title)s - %(section_number)02d - %(section_title)s.%(ext)s'
playlist_item_limit = int(playlist_item_limit) playlist_item_limit = int(playlist_item_limit)

View File

@@ -100,8 +100,6 @@ class Download:
)}) )})
def put_status_postprocessor(d): def put_status_postprocessor(d):
log.debug(f"Postprocessor hook called: postprocessor={d.get('postprocessor')}, status={d.get('status')}")
if d['postprocessor'] == 'MoveFiles' and d['status'] == 'finished': if d['postprocessor'] == 'MoveFiles' and d['status'] == 'finished':
if '__finaldir' in d['info_dict']: if '__finaldir' in d['info_dict']:
filename = os.path.join(d['info_dict']['__finaldir'], os.path.basename(d['info_dict']['filepath'])) filename = os.path.join(d['info_dict']['__finaldir'], os.path.basename(d['info_dict']['filepath']))
@@ -217,7 +215,7 @@ class Download:
self.info.chapter_files = [] self.info.chapter_files = []
rel_path = os.path.relpath(chapter_file, self.download_dir) rel_path = os.path.relpath(chapter_file, self.download_dir)
file_size = os.path.getsize(chapter_file) if os.path.exists(chapter_file) else None file_size = os.path.getsize(chapter_file) if os.path.exists(chapter_file) else None
# Upsert: update if exists, otherwise append. Postprocessor hook called multiple times with chapters. #Postprocessor hook called multiple times with chapters. Only insert if not already present.
existing = next((cf for cf in self.info.chapter_files if cf['filename'] == rel_path), None) existing = next((cf for cf in self.info.chapter_files if cf['filename'] == rel_path), None)
if not existing: if not existing:
self.info.chapter_files.append({'filename': rel_path, 'size': file_size}) self.info.chapter_files.append({'filename': rel_path, 'size': file_size})
@@ -464,7 +462,7 @@ class DownloadQueue:
return {'status': 'ok'} return {'status': 'ok'}
return {'status': 'error', 'msg': f'Unsupported resource "{etype}"'} return {'status': 'error', 'msg': f'Unsupported resource "{etype}"'}
async def add(self, url, quality, format, folder, custom_name_prefix, playlist_strict_mode, playlist_item_limit, auto_start=True, split_by_chapters=False, chapter_template='%(section_number)02d - %(section_title)s.%(ext)s', already=None): async def add(self, url, quality, format, folder, custom_name_prefix, playlist_strict_mode, playlist_item_limit, auto_start=True, split_by_chapters=False, chapter_template='%(title)s - %(section_number)02d - %(section_title)s.%(ext)s', already=None):
log.info(f'adding {url}: {quality=} {format=} {already=} {folder=} {custom_name_prefix=} {playlist_strict_mode=} {playlist_item_limit=} {auto_start=} {split_by_chapters=} {chapter_template=}') log.info(f'adding {url}: {quality=} {format=} {already=} {folder=} {custom_name_prefix=} {playlist_strict_mode=} {playlist_item_limit=} {auto_start=} {split_by_chapters=} {chapter_template=}')
already = set() if already is None else already already = set() if already is None else already
if url in already: if url in already:

View File

@@ -472,6 +472,8 @@
</td> </td>
</tr> </tr>
} }
}
}
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@@ -11,9 +11,9 @@ import { faGithub } from '@fortawesome/free-brands-svg-icons';
import { CookieService } from 'ngx-cookie-service'; import { CookieService } from 'ngx-cookie-service';
import { DownloadsService } from './services/downloads.service'; import { DownloadsService } from './services/downloads.service';
import { Themes } from './theme'; import { Themes } from './theme';
import { Download, Status, Theme, Quality, Format, Formats, State } from './interfaces'; import { Download, Status, Theme , Quality, Format, Formats, State } from './interfaces';
import { EtaPipe, SpeedPipe, FileSizePipe } from './pipes'; import { EtaPipe, SpeedPipe, FileSizePipe } from './pipes';
import { MasterCheckboxComponent, SlaveCheckboxComponent } from './components/'; import { MasterCheckboxComponent , SlaveCheckboxComponent} from './components/';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@@ -106,7 +106,7 @@ export class App implements AfterViewInit, OnInit {
this.quality = this.cookieService.get('metube_quality') || 'best'; this.quality = this.cookieService.get('metube_quality') || 'best';
this.autoStart = this.cookieService.get('metube_auto_start') !== 'false'; this.autoStart = this.cookieService.get('metube_auto_start') !== 'false';
this.splitByChapters = this.cookieService.get('metube_split_chapters') === 'true'; this.splitByChapters = this.cookieService.get('metube_split_chapters') === 'true';
this.chapterTemplate = this.cookieService.get('metube_chapter_template') || '%(section_number)02d - %(section_title)s.%(ext)s'; this.chapterTemplate = this.cookieService.get('metube_chapter_template') || '%(title)s - %(section_number)02d - %(section_title)s.%(ext)s';
this.activeTheme = this.getPreferredTheme(this.cookieService); this.activeTheme = this.getPreferredTheme(this.cookieService);
@@ -186,7 +186,7 @@ export class App implements AfterViewInit, OnInit {
return this.quality == 'audio' || this.format == 'mp3' || this.format == 'm4a' || this.format == 'opus' || this.format == 'wav' || this.format == 'flac'; return this.quality == 'audio' || this.format == 'mp3' || this.format == 'm4a' || this.format == 'opus' || this.format == 'wav' || this.format == 'flac';
} }
getMatchingCustomDir(): Observable<string[]> { getMatchingCustomDir() : Observable<string[]> {
return this.downloads.customDirsChanged.asObservable().pipe( return this.downloads.customDirsChanged.asObservable().pipe(
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
map((output: any) => { map((output: any) => {
@@ -206,12 +206,12 @@ export class App implements AfterViewInit, OnInit {
getYtdlOptionsUpdateTime() { getYtdlOptionsUpdateTime() {
this.downloads.ytdlOptionsChanged.subscribe({ this.downloads.ytdlOptionsChanged.subscribe({
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
next: (data: any) => { next: (data:any) => {
if (data['success']) { if (data['success']){
const date = new Date(data['update_time'] * 1000); const date = new Date(data['update_time'] * 1000);
this.ytDlpOptionsUpdateTime = date.toLocaleString(); this.ytDlpOptionsUpdateTime=date.toLocaleString();
} else { }else{
alert("Error reload yt-dlp options: " + data['msg']); alert("Error reload yt-dlp options: "+data['msg']);
} }
} }
}); });
@@ -269,6 +269,10 @@ export class App implements AfterViewInit, OnInit {
} }
chapterTemplateChanged() { chapterTemplateChanged() {
// Restore default if template is cleared
if (!this.chapterTemplate || this.chapterTemplate.trim() === '') {
this.chapterTemplate = '%(title)s - %(section_number)02d - %(section_title)s.%(ext)s';
}
this.cookieService.set('metube_chapter_template', this.chapterTemplate, { expires: 3650 }); this.cookieService.set('metube_chapter_template', this.chapterTemplate, { expires: 3650 });
} }
@@ -290,7 +294,7 @@ export class App implements AfterViewInit, OnInit {
const exists = this.qualities.find(el => el.id === this.quality) const exists = this.qualities.find(el => el.id === this.quality)
this.quality = exists ? this.quality : 'best' this.quality = exists ? this.quality : 'best'
} }
} }
addDownload(url?: string, quality?: string, format?: string, folder?: string, customNamePrefix?: string, playlistStrictMode?: boolean, playlistItemLimit?: number, autoStart?: boolean, splitByChapters?: boolean, chapterTemplate?: string) { addDownload(url?: string, quality?: string, format?: string, folder?: string, customNamePrefix?: string, playlistStrictMode?: boolean, playlistItemLimit?: number, autoStart?: boolean, splitByChapters?: boolean, chapterTemplate?: string) {
url = url ?? this.addUrl url = url ?? this.addUrl
@@ -304,6 +308,12 @@ export class App implements AfterViewInit, OnInit {
splitByChapters = splitByChapters ?? this.splitByChapters splitByChapters = splitByChapters ?? this.splitByChapters
chapterTemplate = chapterTemplate ?? this.chapterTemplate chapterTemplate = chapterTemplate ?? this.chapterTemplate
// Validate chapter template if chapter splitting is enabled
if (splitByChapters && !chapterTemplate.includes('%(section_number)')) {
alert('Chapter template must include %(section_number)');
return;
}
console.debug('Downloading: url=' + url + ' quality=' + quality + ' format=' + format + ' folder=' + folder + ' customNamePrefix=' + customNamePrefix + ' playlistStrictMode=' + playlistStrictMode + ' playlistItemLimit=' + playlistItemLimit + ' autoStart=' + autoStart + ' splitByChapters=' + splitByChapters + ' chapterTemplate=' + chapterTemplate); console.debug('Downloading: url=' + url + ' quality=' + quality + ' format=' + format + ' folder=' + folder + ' customNamePrefix=' + customNamePrefix + ' playlistStrictMode=' + playlistStrictMode + ' playlistItemLimit=' + playlistItemLimit + ' autoStart=' + autoStart + ' splitByChapters=' + splitByChapters + ' chapterTemplate=' + chapterTemplate);
this.addInProgress = true; this.addInProgress = true;
this.downloads.add(url, quality, format, folder, customNamePrefix, playlistStrictMode, playlistItemLimit, autoStart, splitByChapters, chapterTemplate).subscribe((status: Status) => { this.downloads.add(url, quality, format, folder, customNamePrefix, playlistStrictMode, playlistItemLimit, autoStart, splitByChapters, chapterTemplate).subscribe((status: Status) => {
@@ -329,7 +339,7 @@ export class App implements AfterViewInit, OnInit {
this.downloads.delById(where, [id]).subscribe(); this.downloads.delById(where, [id]).subscribe();
} }
startSelectedDownloads(where: State) { startSelectedDownloads(where: State){
this.downloads.startByFilter(where, dl => !!dl.checked).subscribe(); this.downloads.startByFilter(where, dl => !!dl.checked).subscribe();
} }

View File

@@ -151,7 +151,7 @@ export class DownloadsService {
const defaultPlaylistItemLimit = 0; const defaultPlaylistItemLimit = 0;
const defaultAutoStart = true; const defaultAutoStart = true;
const defaultSplitByChapters = false; const defaultSplitByChapters = false;
const defaultChapterTemplate = '%(section_number)02d - %(section_title)s.%(ext)s'; const defaultChapterTemplate = '%(title)s - %(section_number)02d - %(section_title)s.%(ext)s';
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.add(url, defaultQuality, defaultFormat, defaultFolder, defaultCustomNamePrefix, defaultPlaylistStrictMode, defaultPlaylistItemLimit, defaultAutoStart, defaultSplitByChapters, defaultChapterTemplate) this.add(url, defaultQuality, defaultFormat, defaultFolder, defaultCustomNamePrefix, defaultPlaylistStrictMode, defaultPlaylistItemLimit, defaultAutoStart, defaultSplitByChapters, defaultChapterTemplate)