Skip to content

Commit

Permalink
feat(options): add descriptions to options and enable configurable tr…
Browse files Browse the repository at this point in the history
…ack title pattern
  • Loading branch information
xtangle committed Nov 4, 2018
1 parent 7a5ee30 commit dda2e7d
Show file tree
Hide file tree
Showing 48 changed files with 359 additions and 203 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ This extension will add download buttons to:
* Tracks that are contained in scrollable lists.

When a download is initiated, it attempts to download a track in its uploaded format (i.e. highest quality) and will only
resort to the streamable 128kb mp3 version if no better quality is available.
resort to the streamable 128 kbps mp3 version if no better quality is available.

Metadata information and cover art is automatically added to files in .mp3 format (in ID3v2.3).

Expand Down
93 changes: 78 additions & 15 deletions src/resources/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,31 @@
<title>ZoundCloud Options</title>
<style type="text/css">
body {
margin: 1em 1.5em 1.5em;
margin: 0 1.5em 1.5em;
}
.top-links {
text-align: right;
margin-bottom: 0.5em;
}
.option {
font-size: 12px;
padding-bottom: 0.5em;
}
.option label,input[type=checkbox] {
cursor: pointer;
.option label {
font-weight: 500;
}
.option input[type=checkbox] {
transform: scale(1.25);
vertical-align: middle;
margin-right: 0.5em;
}
.option label,input[type=checkbox],summary {
cursor: pointer;
}
.option details,textarea {
margin: 0.5em 0 0.5em 2em;
}
.option summary {
width: fit-content;
}
.control > * {
margin-top: 1em;
Expand All @@ -34,34 +46,85 @@
</style>
</head>
<body>
<div class="top-links">
<a target="_blank" href="https://github.com/xtangle/zoundcloud/">Website</a> |
<a target="_blank" href="https://github.com/xtangle/zoundcloud/issues/new">Send feedback</a>
</div>
<div class="option">
<label>
<input type="checkbox" id="add-metadata-option">
<input type="checkbox" id="add-metadata-option">
<label for="add-metadata-option">
Add Metadata to mp3 files.
</label>
<details>
<summary>Expand for details.</summary>
<p>
This option adds ID3v2 metadata tags to all tracks downloaded that have the .mp3 extension.
Metadata data is fetched from SoundCloud's API.
</p>
<p>
The added metadata includes: cover art, title, album artist, genres, duration, release year,
bpm, artist url, audio source url, and description.
</p>
<p>
Note that if this option is enabled, the entire song needs to be downloaded to memory first.
This might cause a song to not appear to be downloading at first, but in most cases
it is actually downloading in the background.
</p>
</details>
</div>
<div class="option">
<label>
<input type="checkbox" id="always-mp3-option">
<input type="checkbox" id="always-mp3-option">
<label for="always-mp3-option">
Always download tracks in .mp3 format.
</label>
<details>
<summary>Expand for details.</summary>
<p>
Enabling this option will cause all tracks downloaded to be in mp3 format.
Most of the time, this will be the 128 kbps bit rate streamable version you hear
during a normal browsing session on SoundCloud.
</p>
<p>
If this option is disabled, the original uploaded format of a track (often of higher quality)
will take precedence when downloading. However, metadata will not be added to non-mp3 formats.
</p>
</details>
</div>
<div class="option">
<label>
<input type="checkbox" id="clean-title-option">
Remove text containing 'Free Download' from track titles.
<input type="checkbox" id="clean-title-option">
<label for="clean-title-option">
Remove text from track titles matching the pattern:
</label>
<textarea id="clean-title-pattern" cols="40" rows="5" wrap="soft"></textarea>
<details>
<summary>Expand for details.</summary>
<p>
Before metadata is added and the file name is established, if the track title contains
a substring matching the provided regular expression pattern, the matching part will be removed.
One exception is that if the regex matches the entire track title, then it will not be removed.
</p>
<p>
The default setting attempts to remove strings containing 'Free Download' or
'Download Link' suffixes appended to the end of many track titles.
</p>
<p>
When entering a custom regular expression, make sure it conforms to Javascript's
<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp">RegExp</a> format.
The provided regex string will be passed into a RegExp constructor with
the global flag turned off and the case insensitive flag turned on.
</p>
</details>
</div>
<div class="option">
<label>
<input type="checkbox" id="overwrite-option">
<input type="checkbox" id="overwrite-option">
<label for="overwrite-option">
Overwrite existing files with the same file name.
</label>
</div>
<div class="control">
<button id="save-btn">Save</button>
<button id="reset-btn">Reset</button>
<span id="confirm-msg" hidden>Settings saved.</span>
<button id="defaults-btn">Defaults</button>
<span id="confirm-msg" hidden>Options saved.</span>
</div>
<script type="text/javascript" src="vendor.js"></script>
<script type="text/javascript" src="options.js"></script>
Expand Down
4 changes: 2 additions & 2 deletions src/ts/background/background-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class BackgroundScript implements IRunnable {
ExtensionMessenger.onMessage$(RequestContentPageReloadMessage.TYPE)
.pipe(takeUntil(this.onSuspend$))
.subscribe((args: IMessageHandlerArgs<RequestContentPageReloadMessage>) =>
ExtensionMessenger.sendToContentPage$(args.sender.tab.id, new ReloadContentPageMessage())
ExtensionMessenger.sendToContentPage$(args.sender.tab.id, new ReloadContentPageMessage()),
);

ExtensionMessenger.onMessage$(RequestDownloadMessage.TYPE)
Expand All @@ -46,7 +46,7 @@ export class BackgroundScript implements IRunnable {
ExtensionMessenger.onMessage$(LogToConsoleMessage.TYPE)
.pipe(takeUntil(this.onSuspend$))
.subscribe((args: IMessageHandlerArgs<LogToConsoleMessage>) =>
logger.log(`${args.message.message} (tabId: ${args.sender.tab.id})`, ...args.message.optionalParams)
logger.log(`${args.message.message} (tabId: ${args.sender.tab.id})`, ...args.message.optionalParams),
);

logger.log('Loaded background script');
Expand Down
12 changes: 6 additions & 6 deletions src/ts/background/sc-page-observables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const initialNavigationToScPage$: Observable<number> =
fromEventPattern<number>((handler: (tabId: number) => void) =>
chrome.webNavigation.onDOMContentLoaded.addListener(
(details: WebNavigationFramedCallbackDetails) => handler(details.tabId),
{url: [{hostEquals: SC_URL_HOST}]}
)
{url: [{hostEquals: SC_URL_HOST}]},
),
);

/**
Expand All @@ -22,18 +22,18 @@ const navigateBetweenScPages$: Observable<number> =
fromEventPattern<number>((handler: (tabId: number) => void) =>
chrome.webNavigation.onHistoryStateUpdated.addListener(
(details: WebNavigationTransitionCallbackDetails) => handler(details.tabId),
{url: [{hostEquals: SC_URL_HOST}]}
)
{url: [{hostEquals: SC_URL_HOST}]},
),
).pipe(debounceTime(20));

const tabExists$: (tabId: number) => Observable<boolean> =
(tabId: number) => bindCallback(chrome.tabs.get)(tabId).pipe(
map((tab: Tab) => !chrome.runtime.lastError && tab !== undefined)
map((tab: Tab) => !chrome.runtime.lastError && tab !== undefined),
);

export const ScPageObservables = {
goToSoundCloudPage$(): Observable<number> {
return merge(initialNavigationToScPage$, navigateBetweenScPages$)
.pipe(concatFilter(tabExists$));
}
},
};
2 changes: 1 addition & 1 deletion src/ts/content/bootstrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const Bootstrapper = {
.subscribe(() => contentPage.unload());
addIdTagToDOM(idTag);
}
}
},
};

function idTagIsInDOM(): boolean {
Expand Down
2 changes: 1 addition & 1 deletion src/ts/content/injection/download-button-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const DownloadButtonFactory = {
const dlButton = createDlButton();
addDlButtonBehavior(dlButton, onUnload$, resourceInfoUrl);
return dlButton;
}
},
};

function createDlButton(): JQuery<HTMLElement> {
Expand Down
2 changes: 1 addition & 1 deletion src/ts/content/injection/injection-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ export const InjectionService = {
ListenEngagementInjectionService.injectDownloadButtons(onUnload$);
ListItemInjectionService.injectDownloadButtons(onUnload$);
UserInfoBarInjectionService.injectDownloadButtons(onUnload$);
}
},
};
10 changes: 5 additions & 5 deletions src/ts/content/injection/injection-signal-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@ export const InjectionSignalFactory = {
create$(selector: string): Observable<JQuery<HTMLElement>> {
return merge(
elementExistOrAdded$(selector),
forcefullyInjectSignal$(selector)
forcefullyInjectSignal$(selector),
).pipe(
map(toJQuery),
filter(hasNoDownloadButton)
filter(hasNoDownloadButton),
);
}
},
};

function forcefullyInjectSignal$(selector: string): Observable<Node> {
return merge(
ContentPageMessenger.onMessage$(ReloadContentPageMessage.TYPE).pipe(
switchMapTo(interval(200).pipe(take(20)))
switchMapTo(interval(200).pipe(take(20))),
),
interval(1000),
).pipe(
switchMapTo(elementExist$(selector))
switchMapTo(elementExist$(selector)),
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/ts/content/injection/list-item-injection-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const ListItemInjectionService = {
InjectionSignalFactory.create$(selector)
.pipe(takeUntil(onUnload$))
.subscribe(addToListItem.bind(null, onUnload$));
}
},
};

function createDownloadButton(onUnload$: Observable<any>, downloadInfoUrl: string): JQuery<HTMLElement> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const ListenEngagementInjectionService = {
InjectionSignalFactory.create$(selector)
.pipe(takeUntil(onUnload$))
.subscribe(addToListenEngagement.bind(null, onUnload$));
}
},
};

function createDownloadButton(onUnload$: Observable<any>): JQuery<HTMLElement> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const UserInfoBarInjectionService = {
InjectionSignalFactory.create$(selector)
.pipe(takeUntil(onUnload$))
.subscribe(addToUserInfoBar.bind(null, onUnload$));
}
},
};

function createDownloadButton(onUnload$: Observable<any>): JQuery<HTMLElement> {
Expand Down
6 changes: 3 additions & 3 deletions src/ts/download/download-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
IResourceInfo,
ITrackInfo,
IUserInfo,
ResourceType
ResourceType,
} from 'src/ts/download/resource/resource-info';
import {ResourceInfoService} from 'src/ts/download/resource/resource-info-service';
import {TrackDownloadService} from 'src/ts/download/track-download-service';
Expand All @@ -21,10 +21,10 @@ export const DownloadService = {
.pipe(timeout(30000))
.subscribe(
doDownload.bind(null, downloadResult$, resourceInfoUrl),
onError.bind(null, downloadResult$, resourceInfoUrl)
onError.bind(null, downloadResult$, resourceInfoUrl),
);
return downloadResult$.asObservable();
}
},
};

function doDownload(downloadResult$: AsyncSubject<IDownloadResult>,
Expand Down
18 changes: 9 additions & 9 deletions src/ts/download/metadata/id3-metadata-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ export const ID3MetadataService = {
...downloadInfo,
downloadOptions: {
...downloadInfo.downloadOptions,
url
}
url,
},
} as any)),
catchError((err: Error) => {
logger.error(`Unable to fetch metadata for track ${downloadInfo.trackInfo.title}`, err);
return of(downloadInfo);
})
}),
);
}
},
};

function writeMetadata$(metadata: ITrackMetadata, arrayBuffer: ArrayBuffer): Observable<IID3Writer> {
return of(ID3WriterService.createWriter(arrayBuffer)).pipe(
map(withTextualMetadata.bind(null, metadata)),
flatMap(withCoverArt$.bind(null, metadata)),
map(ID3WriterService.addTag)
map(ID3WriterService.addTag),
);
}

Expand All @@ -52,7 +52,7 @@ function withTextualMetadata(metadata: ITrackMetadata, writer: IID3Writer): IID3
ID3WriterService.setFrame(writer, 'WOAS', metadata.audio_source_url);
ID3WriterService.setFrame(writer, 'COMM', {
description: 'Soundcloud description',
text: metadata.description || ''
text: metadata.description || '',
});
return writer;
}
Expand All @@ -69,12 +69,12 @@ function withCoverArt$(metadata: ITrackMetadata, writer: IID3Writer): Observable
data: arrayBuffer,
description: `Soundcloud artwork. Source: ${url}`,
type: 3,
useUnicodeEncoding: false
})
useUnicodeEncoding: false,
}),
),
catchError((err: Error) => {
logger.error(`Unable to fetch cover art for track ${metadata.title}`, err);
return of(writer);
})
}),
);
}
6 changes: 3 additions & 3 deletions src/ts/download/metadata/metadata-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ export const MetadataAdapter = {
addMetadata$(downloadInfo: ITrackDownloadInfo): Observable<ITrackDownloadInfo> {
const fileExtension = downloadInfo.downloadOptions.filename.split('.').pop();
const metadata$ = of(TrackMetadataFactory.create(downloadInfo.trackInfo)).pipe(
flatMap(withUpdatedCoverArtUrl$)
flatMap(withUpdatedCoverArtUrl$),
);

switch (fileExtension) {
case 'mp3':
return metadata$.pipe(
flatMap((metadata: ITrackMetadata) => ID3MetadataService.addID3V2Metadata$(metadata, downloadInfo)),
tap((info: ITrackDownloadInfo) => logger.debug('Added mp3 metadata', info))
tap((info: ITrackDownloadInfo) => logger.debug('Added mp3 metadata', info)),
);
default:
return of(downloadInfo);
}
}
},
};

function withUpdatedCoverArtUrl$(metadata: ITrackMetadata): Observable<ITrackMetadata> {
Expand Down
4 changes: 2 additions & 2 deletions src/ts/download/metadata/track-metadata-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const TrackMetadataFactory = {
release_day: trackInfo.release_day,
release_month: trackInfo.release_month,
release_year: trackInfo.release_year,
title: (titleParts.length > 1) ? titleParts.slice(1).join(' - ') : trackInfo.title
title: (titleParts.length > 1) ? titleParts.slice(1).join(' - ') : trackInfo.title,
};
}
},
};
6 changes: 3 additions & 3 deletions src/ts/download/playlist-download-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ export const PlaylistDownloadService = {
const downloadLocation = getDownloadLocation(playlistInfo);
logger.debug(`Downloading playlist to '${downloadLocation}'`, playlistInfo);
const tracks = playlistInfo.tracks.map(
(trackInfo: ITrackInfo) => TrackDownloadService.download(trackInfo, downloadLocation)
(trackInfo: ITrackInfo) => TrackDownloadService.download(trackInfo, downloadLocation),
);
return {
kind: ResourceType.Playlist,
playlistInfo,
tracks
tracks,
};
}
},
};

function getDownloadLocation(playlistInfo: IPlaylistInfo): string {
Expand Down
Loading

0 comments on commit dda2e7d

Please sign in to comment.