Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import watch history from Youtube. #6408

Open
3 tasks done
PapiJalopy opened this issue Aug 24, 2024 · 19 comments · Fixed by #6543
Open
3 tasks done

Import watch history from Youtube. #6408

PapiJalopy opened this issue Aug 24, 2024 · 19 comments · Fixed by #6543
Labels
enhancement New feature or request

Comments

@PapiJalopy
Copy link

Describe your suggested feature

Not sure if this has been discussed before, I searched for it in closed and open issues and didn't find any mention of it. Sorry if i missed it.

Feature request for importing Youtube watch history.

I see the current in app watch history implementation has certain "keys" such as "uploadDate", "uploader", "uploaderUrl", "uploaderAvatar", "thumbnailUrl" and "duration watched"

The Youtube .csv only contains some basic "keys" - "header", "url", "titleURL", "title" and "name".

Idk if certain parts can be omitted and still have a functional watch history or if this would require refactoring the entire watch history feature.

Other details

No response

Acknowledgements

  • I have searched the existing issues and this is a new ticket, NOT a duplicate or related to another open issue.
  • I have written a short but informative title.
  • I will fill out all of the requested information in this form.
@PapiJalopy PapiJalopy added the enhancement New feature or request label Aug 24, 2024
@Bnyro
Copy link
Member

Bnyro commented Sep 3, 2024

Could you please share an example file? (It's sufficient if you remove anything but a single video)

@Familex
Copy link

Familex commented Sep 3, 2024

@Bnyro json option looks like this (by default html exports):

[{
  "header": "YouTube",
  "title": "Watched MASTER BOOT RECORD - CPU",
  "titleUrl": "https://www.youtube.com/watch?v\u003dRNOUJZlR0p4",
  "subtitles": [{
    "name": "MASTER BOOT RECORD",
    "url": "https://www.youtube.com/channel/UCEVndLRqBDNcZhBdWo5oepg"
  }],
  "time": "2024-09-03T17:58:45.124Z",
  "products": ["YouTube"],
  "activityControls": ["YouTube watch history"]
},{
  "header": "YouTube",
  "title": "Watched ​",
  "titleUrl": "https://www.youtube.com/watch?v\u003daUG--f5qZjo",
  "subtitles": [{
    "name": "made from dreams",
    "url": "https://www.youtube.com/channel/UCI7ZJqTa3KZ_BzU24O3ex8w"
  }],
  "time": "2024-09-03T16:14:01.445Z",
  "products": ["YouTube"],
  "activityControls": ["YouTube watch history"]
}]

@Bnyro
Copy link
Member

Bnyro commented Sep 25, 2024

That export would be importable without any thumbnails just fine, but if we want the thumbnails we would need to do one API request per video to get additional info such as the thumbnail, duration and channel avatar which will make the import very slow and unreliable.

Alternatively, we can "guess" the thumbnail URLs such as https://img.youtube.com/vi/{videoId}/mqdefault.jpg which will probably work for 99% of the videos.

That way everything would be there except for

  • channel avatars
  • video duration

If that's okay for you, I'll probably implement that before the next release - otherwise I think we should drop this feature because I doubt that doing one API request per video import will work well and would be worth it, we've already had several issues doing so when importing playlists.

@PapiJalopy
Copy link
Author

Honestly that sounds fine, if you wanna give it the green light I feel like that should be sufficient.

@Bnyro
Copy link
Member

Bnyro commented Sep 26, 2024

The timestamp YouTube provides is the time you watched the video - not the time it got uploaded, so we're missing that field too :/

So we can import (tested with your sample)

  • videoId
  • video title
  • uploader name
  • uploader channel url
  • thumbnail (by guessing)

@Notesure
Copy link

An error occurs when importing

@Bnyro
Copy link
Member

Bnyro commented Sep 26, 2024

When importing what?

@Bnyro
Copy link
Member

Bnyro commented Sep 26, 2024

I've successfully tested the file above.

@Notesure
Copy link

I tried json and html formats, it gives an error
https://github.com/menggatot/youtube-watch-history-to-csv I followed it

@Familex
Copy link

Familex commented Sep 26, 2024

@Notesure did you change the format before exporting?

image

@Notesure
Copy link

Notesure commented Sep 26, 2024

I have imported Json and html

kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Expected start of the array '[', but had '<' instead at path: $
JSON input: <title>Fəaliyyət T.....
at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:24)
at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:32)
at kotlinx.serialization.json.internal.AbstractJsonLexer.fail(AbstractJsonLexer.kt:598)
at kotlinx.serialization.json.internal.AbstractJsonLexer.fail$default(AbstractJsonLexer.kt:596)
at kotlinx.serialization.json.internal.AbstractJsonLexer.fail$kotlinx_serialization_json(AbstractJsonLexer.kt:233)
at kotlinx.serialization.json.internal.AbstractJsonLexer.fail$kotlinx_serialization_json$default(AbstractJsonLexer.kt:228)
at kotlinx.serialization.json.internal.AbstractJsonLexer.unexpectedToken(AbstractJsonLexer.kt:225)
at kotlinx.serialization.json.internal.AbstractJsonLexer.consumeNextToken(AbstractJsonLexer.kt:210)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.beginStructure(StreamingJsonDecoder.kt:102)
at kotlinx.serialization.internal.AbstractCollectionSerializer.merge(CollectionSerializers.kt:29)
at kotlinx.serialization.internal.AbstractCollectionSerializer.deserialize(CollectionSerializers.kt:43)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:69)
at kotlinx.serialization.json.internal.JsonStreamsKt.decodeByReader(JsonStreams.kt:43)
at kotlinx.serialization.json.JvmStreamsKt.decodeFromStream(JvmStreams.kt:61)
at com.github.libretube.helpers.ImportHelper.importWatchHistory(ImportHelper.kt:360)
at com.github.libretube.ui.preferences.BackupRestoreSettings$getWatchHistoryFile$1$1$1.invokeSuspend(BackupRestoreSettings.kt:109)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:111)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:99)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:811)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:715)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:702)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@90aefbd, Dispatchers.IO]

@Bnyro
Copy link
Member

Bnyro commented Sep 26, 2024

It only supports JSON imports, not HTML.

@Notesure
Copy link

It only supports JSON imports, not HTML.

But still there is an error, maybe my watch history is too long

@Bnyro
Copy link
Member

Bnyro commented Sep 27, 2024

JSON input: <title>Fəaliyyət T...

That's HTML what you inserted there ...

@Notesure
Copy link

kotlinx.serialization.MissingFieldException: Field 'subtitles' is required for type with serial name 'com.github.libretube.obj.YouTubeWatchHistoryFileItem', but it was missing at path: $[0]
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:95)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:43)
at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:70)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableElement(StreamingJsonDecoder.kt:168)
at kotlinx.serialization.encoding.CompositeDecoder$DefaultImpls.decodeSerializableElement$default(Decoding.kt:538)
at kotlinx.serialization.internal.CollectionLikeSerializer.readElement(CollectionSerializers.kt:80)
at kotlinx.serialization.internal.AbstractCollectionSerializer.readElement$default(CollectionSerializers.kt:51)
at kotlinx.serialization.internal.AbstractCollectionSerializer.merge(CollectionSerializers.kt:36)
at kotlinx.serialization.internal.AbstractCollectionSerializer.deserialize(CollectionSerializers.kt:43)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:69)
at kotlinx.serialization.json.internal.JsonStreamsKt.decodeByReader(JsonStreams.kt:43)
at kotlinx.serialization.json.JvmStreamsKt.decodeFromStream(JvmStreams.kt:61)
at com.github.libretube.helpers.ImportHelper.importWatchHistory(ImportHelper.kt:360)
at com.github.libretube.ui.preferences.BackupRestoreSettings$getWatchHistoryFile$1$1$1.invokeSuspend(BackupRestoreSettings.kt:109)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:111)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:99)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:811)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:715)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:702)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c9e9a91, Dispatchers.IO]
Caused by: kotlinx.serialization.MissingFieldException: Field 'subtitles' is required for type with serial name 'com.github.libretube.obj.YouTubeWatchHistoryFileItem', but it was missing
at kotlinx.serialization.internal.PluginExceptionsKt.throwMissingFieldException(PluginExceptions.kt:20)
at com.github.libretube.obj.YouTubeWatchHistoryFileItem.(YouTubeWatchHistoryFileItem.kt:5)
at com.github.libretube.obj.YouTubeWatchHistoryFileItem$$serializer.deserialize(YouTubeWatchHistoryFileItem.kt:5)
at com.github.libretube.obj.YouTubeWatchHistoryFileItem$$serializer.deserialize(YouTubeWatchHistoryFileItem.kt:5)
at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:69)
... 21 more

@Familex
Copy link

Familex commented Sep 27, 2024

@Bnyro I just tried it and I have the same error... It turns out, that for private videos the scheme is like this. Sorry for misleading

{
  "header": "YouTube",
  "title": "Watched https://www.youtube.com/watch?v\u003d2TXxQQ9ImKU",
  "titleUrl": "https://www.youtube.com/watch?v\u003d2TXxQQ9ImKU",
  "time": "2023-02-10T21:58:42.390Z",
  "products": ["YouTube"],
  "activityControls": ["YouTube watch history"]
}

@Familex
Copy link

Familex commented Sep 27, 2024

I used genson on my watch-history.json file and it produced this json schema: schema.json

@Bnyro Bnyro reopened this Sep 27, 2024
@Bnyro
Copy link
Member

Bnyro commented Sep 27, 2024

I just tried it and I have the same error... It turns out, that for private videos the scheme is like this. Sorry for misleading

I assume we should just skip all private videos? They can't be watched via LibreTube anyways.

@Familex
Copy link

Familex commented Sep 27, 2024

I assume we should just skip all private videos

I think so, anyway, there is no information about them in the file except for the link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants